2025-12-06 12:46:40 +09:00
|
|
|
import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock"
|
|
|
|
|
import { createAnthropic } from "@ai-sdk/anthropic"
|
|
|
|
|
import { azure, createAzure } from "@ai-sdk/azure"
|
|
|
|
|
import { createDeepSeek, deepseek } from "@ai-sdk/deepseek"
|
2025-12-18 21:00:21 +08:00
|
|
|
import { createGateway, gateway } from "@ai-sdk/gateway"
|
2025-12-06 12:46:40 +09:00
|
|
|
import { createGoogleGenerativeAI, google } from "@ai-sdk/google"
|
2026-01-11 00:11:30 -05:00
|
|
|
import { createVertex } from "@ai-sdk/google-vertex"
|
2025-12-06 12:46:40 +09:00
|
|
|
import { createOpenAI, openai } from "@ai-sdk/openai"
|
|
|
|
|
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
|
|
|
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider"
|
|
|
|
|
import { createOllama, ollama } from "ollama-ai-provider-v2"
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
import { PROVIDER_INFO, type ProviderName } from "@/lib/types/model-config"
|
2025-11-15 13:36:42 +09:00
|
|
|
|
2026-01-10 14:06:17 +09:00
|
|
|
export type { ProviderName }
|
2025-11-15 13:36:42 +09:00
|
|
|
|
|
|
|
|
interface ModelConfig {
|
2025-12-06 12:46:40 +09:00
|
|
|
model: any
|
|
|
|
|
providerOptions?: any
|
|
|
|
|
headers?: Record<string, string>
|
|
|
|
|
modelId: string
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
provider: ProviderName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Providers that only support a single system message
|
|
|
|
|
export const SINGLE_SYSTEM_PROVIDERS = new Set<ProviderName>([
|
|
|
|
|
"minimax",
|
|
|
|
|
"glm",
|
|
|
|
|
"qwen",
|
|
|
|
|
"kimi",
|
|
|
|
|
"qiniu",
|
2026-03-24 19:48:34 +08:00
|
|
|
"novita",
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
])
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Normalize MiniMax base URL for AI SDK compatibility.
|
|
|
|
|
* MiniMax supports Anthropic-compatible and OpenAI-compatible endpoints.
|
|
|
|
|
*/
|
|
|
|
|
export function normalizeMiniMaxBaseURL(rawUrl: string): {
|
|
|
|
|
baseURL: string
|
|
|
|
|
isAnthropicCompatible: boolean
|
|
|
|
|
} {
|
|
|
|
|
const isAnthropicCompatible = rawUrl.includes("/anthropic")
|
|
|
|
|
let baseURL = rawUrl.replace(/\/$/, "")
|
|
|
|
|
if (isAnthropicCompatible) {
|
|
|
|
|
if (!baseURL.endsWith("/anthropic/v1")) {
|
|
|
|
|
if (baseURL.endsWith("/anthropic")) {
|
|
|
|
|
baseURL = `${baseURL}/v1`
|
|
|
|
|
} else {
|
|
|
|
|
baseURL = `${baseURL}/anthropic/v1`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!baseURL.endsWith("/v1")) {
|
|
|
|
|
baseURL = `${baseURL}/v1`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return { baseURL, isAnthropicCompatible }
|
2025-11-15 13:36:42 +09:00
|
|
|
}
|
|
|
|
|
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
export interface ClientOverrides {
|
|
|
|
|
provider?: string | null
|
|
|
|
|
baseUrl?: string | null
|
|
|
|
|
apiKey?: string | null
|
|
|
|
|
modelId?: string | null
|
feat: multi-provider model configuration with UI/UX improvements (#355)
* feat: add multi-provider model configuration
- Add model config dialog for managing multiple AI providers
- Support for OpenAI, Anthropic, Google, Azure, Bedrock, OpenRouter, DeepSeek, SiliconFlow, Ollama, and AI Gateway
- Add model selector dropdown in chat panel header
- Add API key validation endpoint
- Add custom model ID input with keyboard navigation
- Fix hover highlight in Command component
- Add suggested models for each provider including latest Claude 4.5 series
- Store configuration locally in browser
* feat: improve model config UI and move selector to chat input
- Move model selector from header to chat input (left of send button)
- Add per-model validation status (queued, running, valid, invalid)
- Filter model selector to only show verified models
- Add editable model IDs in config dialog
- Add custom model input field alongside suggested models dropdown
- Fix hover states on provider buttons and select triggers
- Update OpenAI suggested models with GPT-5 series
- Add alert-dialog component for delete confirmation
* refactor: revert shadcn component changes, apply hover fix at usage site
* feat: add AWS credentials support for Bedrock provider
- Add AWS Access Key ID, Secret Access Key, Region fields for Bedrock
- Show different credential fields based on provider type
- Update validation API to handle Bedrock with AWS credentials
- Add region selector with common AWS regions
* fix: reset Test button after validation completes
* fix: reset validation button to Test after success
* fix: complete bedrock support and UI/UX improvements
- Add bedrock to ALLOWED_CLIENT_PROVIDERS for client credentials
- Pass AWS credentials through full chain (headers → API → provider)
- Replace non-existent GPT-5 models with real ones (o1, o3-mini)
- Add accessibility: aria-labels, focus-visible rings, inline errors
- Add more AWS regions (Ohio, London, Paris, Mumbai, Seoul, São Paulo)
- Fix setTimeout cleanup with useRef on component unmount
- Fix TypeScript type consistency in getSelectedAIConfig fallback
* chore: remove unused code
- Remove unused setAccessCodeRequired state in chat-panel.tsx
- Remove unused getSelectedModel export in model-config.ts
* fix: UI/UX improvements for model configuration dialog
- Add gradient header styling with icon badge
- Change Configuration section icon from Key to Settings2
- Add duplicate model detection with warning banner and inline removal
- Filter out already-added models from suggestions dropdown
- Add type-to-confirm for deleting providers with 3+ models
- Enhance delete confirmation dialog with warning icon
- Improve model selector discoverability (show model name + chevron)
- Add truncation for long model names with title tooltip
- Remove AI provider settings from Settings dialog (now in Model Config)
- Extract ValidationButton into reusable component
* fix: prevent duplicate model IDs within same provider
- Block adding model if ID already exists in provider
- Block editing model ID to match existing model in provider
* fix: improve duplicate model ID notifications
- Add toast notification when trying to add duplicate model
- Allow free typing when editing model ID, validate on blur
- Show warning toast instead of blocking input
* fix: improve duplicate model validation UX in config dialog
- Add inline error display for duplicate model IDs
- Show red border on input when error exists
- Validate on blur with shake animation for edit errors
- Prevent saving empty model names
- Clear errors when user starts typing
- Simplify error styling (small red text, no heavy chips)
2025-12-22 22:36:36 +09:00
|
|
|
// AWS Bedrock credentials
|
|
|
|
|
awsAccessKeyId?: string | null
|
|
|
|
|
awsSecretAccessKey?: string | null
|
|
|
|
|
awsRegion?: string | null
|
|
|
|
|
awsSessionToken?: string | null
|
2026-01-12 14:42:32 -05:00
|
|
|
// Vertex AI config
|
2026-01-14 03:03:30 -05:00
|
|
|
vertexApiKey?: string | null // Express Mode API key
|
2025-12-30 21:13:22 +08:00
|
|
|
// Custom headers (e.g., for EdgeOne cookie auth)
|
|
|
|
|
headers?: Record<string, string>
|
2026-02-02 15:54:35 +09:00
|
|
|
// Custom env var name(s) for server models
|
|
|
|
|
// Can be a single string or array of strings for load balancing
|
|
|
|
|
apiKeyEnv?: string | string[]
|
2026-01-15 21:28:22 +05:30
|
|
|
baseUrlEnv?: string
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 21:24:48 +09:00
|
|
|
// Providers that can be selected from client settings
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
const ALLOWED_CLIENT_PROVIDERS: ProviderName[] = [
|
|
|
|
|
"openai",
|
|
|
|
|
"anthropic",
|
|
|
|
|
"google",
|
2026-01-11 00:11:30 -05:00
|
|
|
"vertexai",
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
"azure",
|
feat: multi-provider model configuration with UI/UX improvements (#355)
* feat: add multi-provider model configuration
- Add model config dialog for managing multiple AI providers
- Support for OpenAI, Anthropic, Google, Azure, Bedrock, OpenRouter, DeepSeek, SiliconFlow, Ollama, and AI Gateway
- Add model selector dropdown in chat panel header
- Add API key validation endpoint
- Add custom model ID input with keyboard navigation
- Fix hover highlight in Command component
- Add suggested models for each provider including latest Claude 4.5 series
- Store configuration locally in browser
* feat: improve model config UI and move selector to chat input
- Move model selector from header to chat input (left of send button)
- Add per-model validation status (queued, running, valid, invalid)
- Filter model selector to only show verified models
- Add editable model IDs in config dialog
- Add custom model input field alongside suggested models dropdown
- Fix hover states on provider buttons and select triggers
- Update OpenAI suggested models with GPT-5 series
- Add alert-dialog component for delete confirmation
* refactor: revert shadcn component changes, apply hover fix at usage site
* feat: add AWS credentials support for Bedrock provider
- Add AWS Access Key ID, Secret Access Key, Region fields for Bedrock
- Show different credential fields based on provider type
- Update validation API to handle Bedrock with AWS credentials
- Add region selector with common AWS regions
* fix: reset Test button after validation completes
* fix: reset validation button to Test after success
* fix: complete bedrock support and UI/UX improvements
- Add bedrock to ALLOWED_CLIENT_PROVIDERS for client credentials
- Pass AWS credentials through full chain (headers → API → provider)
- Replace non-existent GPT-5 models with real ones (o1, o3-mini)
- Add accessibility: aria-labels, focus-visible rings, inline errors
- Add more AWS regions (Ohio, London, Paris, Mumbai, Seoul, São Paulo)
- Fix setTimeout cleanup with useRef on component unmount
- Fix TypeScript type consistency in getSelectedAIConfig fallback
* chore: remove unused code
- Remove unused setAccessCodeRequired state in chat-panel.tsx
- Remove unused getSelectedModel export in model-config.ts
* fix: UI/UX improvements for model configuration dialog
- Add gradient header styling with icon badge
- Change Configuration section icon from Key to Settings2
- Add duplicate model detection with warning banner and inline removal
- Filter out already-added models from suggestions dropdown
- Add type-to-confirm for deleting providers with 3+ models
- Enhance delete confirmation dialog with warning icon
- Improve model selector discoverability (show model name + chevron)
- Add truncation for long model names with title tooltip
- Remove AI provider settings from Settings dialog (now in Model Config)
- Extract ValidationButton into reusable component
* fix: prevent duplicate model IDs within same provider
- Block adding model if ID already exists in provider
- Block editing model ID to match existing model in provider
* fix: improve duplicate model ID notifications
- Add toast notification when trying to add duplicate model
- Allow free typing when editing model ID, validate on blur
- Show warning toast instead of blocking input
* fix: improve duplicate model validation UX in config dialog
- Add inline error display for duplicate model IDs
- Show red border on input when error exists
- Validate on blur with shake animation for edit errors
- Prevent saving empty model names
- Clear errors when user starts typing
- Simplify error styling (small red text, no heavy chips)
2025-12-22 22:36:36 +09:00
|
|
|
"bedrock",
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
"openrouter",
|
|
|
|
|
"deepseek",
|
|
|
|
|
"siliconflow",
|
2025-12-22 22:13:45 +08:00
|
|
|
"sglang",
|
2025-12-16 22:43:33 -05:00
|
|
|
"gateway",
|
2025-12-30 21:13:22 +08:00
|
|
|
"edgeone",
|
2026-01-29 21:24:48 +09:00
|
|
|
"ollama",
|
2025-12-29 11:30:58 +09:00
|
|
|
"doubao",
|
2026-01-06 18:41:25 +08:00
|
|
|
"modelscope",
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
"glm",
|
|
|
|
|
"qwen",
|
|
|
|
|
"qiniu",
|
|
|
|
|
"kimi",
|
|
|
|
|
"minimax",
|
2026-03-24 19:48:34 +08:00
|
|
|
"novita",
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
]
|
|
|
|
|
|
2025-11-30 16:34:42 +09:00
|
|
|
// Bedrock provider options for Anthropic beta features
|
|
|
|
|
const BEDROCK_ANTHROPIC_BETA = {
|
2025-12-06 12:46:40 +09:00
|
|
|
bedrock: {
|
|
|
|
|
anthropicBeta: ["fine-grained-tool-streaming-2025-05-14"],
|
|
|
|
|
},
|
|
|
|
|
}
|
2025-11-30 16:34:42 +09:00
|
|
|
|
|
|
|
|
// Direct Anthropic API headers for beta features
|
|
|
|
|
const ANTHROPIC_BETA_HEADERS = {
|
2025-12-06 12:46:40 +09:00
|
|
|
"anthropic-beta": "fine-grained-tool-streaming-2025-05-14",
|
|
|
|
|
}
|
2025-11-15 13:36:42 +09:00
|
|
|
|
2026-01-13 22:14:45 +09:00
|
|
|
/**
|
|
|
|
|
* Resolve baseURL based on whether user is providing their own API key.
|
|
|
|
|
* When user provides their own API key, we should NOT fall back to server's
|
|
|
|
|
* baseURL environment variable - user credentials should only be sent to
|
|
|
|
|
* user-specified endpoints or official provider endpoints.
|
|
|
|
|
*
|
|
|
|
|
* @param userApiKey - User-provided API key (if any)
|
|
|
|
|
* @param userBaseUrl - User-provided base URL (if any)
|
|
|
|
|
* @param serverBaseUrl - Server's base URL from environment variable
|
|
|
|
|
* @param defaultBaseUrl - Provider's official/default base URL (optional)
|
|
|
|
|
* @returns The resolved base URL to use
|
|
|
|
|
*/
|
|
|
|
|
export function resolveBaseURL(
|
|
|
|
|
userApiKey: string | null | undefined,
|
|
|
|
|
userBaseUrl: string | null | undefined,
|
|
|
|
|
serverBaseUrl: string | undefined,
|
|
|
|
|
defaultBaseUrl?: string,
|
|
|
|
|
): string | undefined {
|
|
|
|
|
if (userApiKey) {
|
|
|
|
|
// User provides their own API key - only use user's baseUrl or default
|
|
|
|
|
return userBaseUrl || defaultBaseUrl || undefined
|
|
|
|
|
}
|
|
|
|
|
// No user API key - fall back to server config
|
|
|
|
|
return userBaseUrl || serverBaseUrl || defaultBaseUrl || undefined
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 21:28:22 +05:30
|
|
|
/**
|
|
|
|
|
* Resolve API key from custom env var name or default env var.
|
|
|
|
|
* Supports multiple API keys per provider via ai-models.json apiKeyEnv config.
|
2026-02-02 15:54:35 +09:00
|
|
|
* When multiple keys are configured, randomly selects one for load balancing.
|
2026-01-15 21:28:22 +05:30
|
|
|
*
|
|
|
|
|
* Priority:
|
|
|
|
|
* 1. User-provided API key (overrides.apiKey)
|
2026-02-02 15:54:35 +09:00
|
|
|
* 2. Custom env var(s) from ai-models.json (overrides.apiKeyEnv)
|
|
|
|
|
* - If array, randomly picks one with a valid value
|
2026-01-15 21:28:22 +05:30
|
|
|
* 3. Default provider env var (defaultEnvVar)
|
|
|
|
|
*/
|
|
|
|
|
function resolveApiKey(
|
|
|
|
|
overrides: ClientOverrides | undefined,
|
|
|
|
|
defaultEnvVar: string,
|
|
|
|
|
): string | undefined {
|
|
|
|
|
if (overrides?.apiKey) return overrides.apiKey
|
2026-02-02 15:54:35 +09:00
|
|
|
|
|
|
|
|
if (overrides?.apiKeyEnv) {
|
|
|
|
|
// Handle array of env var names - randomly select one
|
|
|
|
|
if (Array.isArray(overrides.apiKeyEnv)) {
|
|
|
|
|
// Filter to only env vars that have values
|
|
|
|
|
const validEnvVars = overrides.apiKeyEnv.filter(
|
|
|
|
|
(envVar) => process.env[envVar],
|
|
|
|
|
)
|
|
|
|
|
if (validEnvVars.length > 0) {
|
|
|
|
|
// Randomly select one
|
|
|
|
|
const selectedEnvVar =
|
|
|
|
|
validEnvVars[
|
|
|
|
|
Math.floor(Math.random() * validEnvVars.length)
|
|
|
|
|
]
|
|
|
|
|
console.log(
|
|
|
|
|
`[API Key Routing] Selected ${selectedEnvVar} from ${validEnvVars.length} available keys`,
|
|
|
|
|
)
|
|
|
|
|
return process.env[selectedEnvVar]
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return process.env[overrides.apiKeyEnv]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 21:28:22 +05:30
|
|
|
return process.env[defaultEnvVar]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resolve base URL from custom env var name or default env var.
|
|
|
|
|
* Supports multiple base URLs per provider via ai-models.json baseUrlEnv config.
|
|
|
|
|
*/
|
|
|
|
|
function resolveBaseUrlEnv(
|
|
|
|
|
overrides: ClientOverrides | undefined,
|
|
|
|
|
defaultEnvVar: string,
|
|
|
|
|
): string | undefined {
|
|
|
|
|
if (overrides?.baseUrlEnv) return process.env[overrides.baseUrlEnv]
|
|
|
|
|
return process.env[defaultEnvVar]
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-10 20:54:43 +05:30
|
|
|
/**
|
|
|
|
|
* Safely parse integer from environment variable with validation
|
|
|
|
|
*/
|
|
|
|
|
function parseIntSafe(
|
|
|
|
|
value: string | undefined,
|
|
|
|
|
varName: string,
|
|
|
|
|
min?: number,
|
|
|
|
|
max?: number,
|
|
|
|
|
): number | undefined {
|
|
|
|
|
if (!value) return undefined
|
|
|
|
|
const parsed = Number.parseInt(value, 10)
|
|
|
|
|
if (Number.isNaN(parsed)) {
|
|
|
|
|
throw new Error(`${varName} must be a valid integer, got: ${value}`)
|
|
|
|
|
}
|
|
|
|
|
if (min !== undefined && parsed < min) {
|
|
|
|
|
throw new Error(`${varName} must be >= ${min}, got: ${parsed}`)
|
|
|
|
|
}
|
|
|
|
|
if (max !== undefined && parsed > max) {
|
|
|
|
|
throw new Error(`${varName} must be <= ${max}, got: ${parsed}`)
|
|
|
|
|
}
|
|
|
|
|
return parsed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Build provider-specific options from environment variables
|
|
|
|
|
* Supports various AI SDK providers with their unique configuration options
|
|
|
|
|
*
|
|
|
|
|
* Environment variables:
|
2025-12-23 13:38:50 +09:00
|
|
|
* - OPENAI_REASONING_EFFORT: OpenAI reasoning effort level (minimal/low/medium/high) - for o1/o3/o4/gpt-5
|
|
|
|
|
* - OPENAI_REASONING_SUMMARY: OpenAI reasoning summary (auto/detailed) - auto-enabled for o1/o3/o4/gpt-5
|
2025-12-10 20:54:43 +05:30
|
|
|
* - ANTHROPIC_THINKING_BUDGET_TOKENS: Anthropic thinking budget in tokens (1024-64000)
|
|
|
|
|
* - ANTHROPIC_THINKING_TYPE: Anthropic thinking type (enabled)
|
|
|
|
|
* - GOOGLE_THINKING_BUDGET: Google Gemini 2.5 thinking budget in tokens (1024-100000)
|
2026-01-11 04:19:57 -05:00
|
|
|
* - GOOGLE_THINKING_LEVEL: Google Gemini 3 thinking level (low/high)
|
2026-01-11 00:11:30 -05:00
|
|
|
* - GOOGLE_VERTEX_THINKING_BUDGET: Vertex AI Gemini 2.5 thinking budget in tokens (1024-100000)
|
2026-01-11 04:19:57 -05:00
|
|
|
* - GOOGLE_VERTEX_THINKING_LEVEL: Vertex AI Gemini 3 thinking level (low/high)
|
2025-12-10 20:54:43 +05:30
|
|
|
* - AZURE_REASONING_EFFORT: Azure/OpenAI reasoning effort (low/medium/high)
|
|
|
|
|
* - AZURE_REASONING_SUMMARY: Azure reasoning summary (none/brief/detailed)
|
|
|
|
|
* - BEDROCK_REASONING_BUDGET_TOKENS: Bedrock Claude reasoning budget in tokens (1024-64000)
|
|
|
|
|
* - BEDROCK_REASONING_EFFORT: Bedrock Nova reasoning effort (low/medium/high)
|
|
|
|
|
* - OLLAMA_ENABLE_THINKING: Enable Ollama thinking mode (set to "true")
|
|
|
|
|
*/
|
|
|
|
|
function buildProviderOptions(
|
|
|
|
|
provider: ProviderName,
|
|
|
|
|
modelId?: string,
|
|
|
|
|
): Record<string, any> | undefined {
|
|
|
|
|
const options: Record<string, any> = {}
|
|
|
|
|
|
|
|
|
|
switch (provider) {
|
|
|
|
|
case "openai": {
|
|
|
|
|
const reasoningEffort = process.env.OPENAI_REASONING_EFFORT
|
|
|
|
|
const reasoningSummary = process.env.OPENAI_REASONING_SUMMARY
|
|
|
|
|
|
2025-12-23 13:38:50 +09:00
|
|
|
// OpenAI reasoning models (o1, o3, o4, gpt-5) need reasoningSummary to return thoughts
|
2025-12-10 20:54:43 +05:30
|
|
|
if (
|
|
|
|
|
modelId &&
|
|
|
|
|
(modelId.includes("o1") ||
|
|
|
|
|
modelId.includes("o3") ||
|
2025-12-23 13:38:50 +09:00
|
|
|
modelId.includes("o4") ||
|
2025-12-10 20:54:43 +05:30
|
|
|
modelId.includes("gpt-5"))
|
|
|
|
|
) {
|
|
|
|
|
options.openai = {
|
2025-12-23 13:38:50 +09:00
|
|
|
// Auto-enable reasoning summary for reasoning models
|
|
|
|
|
// Use 'auto' as default since not all models support 'detailed'
|
2025-12-10 20:54:43 +05:30
|
|
|
reasoningSummary:
|
2025-12-23 13:38:50 +09:00
|
|
|
(reasoningSummary as "auto" | "detailed") || "auto",
|
2025-12-10 20:54:43 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Optionally configure reasoning effort
|
|
|
|
|
if (reasoningEffort) {
|
|
|
|
|
options.openai.reasoningEffort = reasoningEffort as
|
|
|
|
|
| "minimal"
|
|
|
|
|
| "low"
|
|
|
|
|
| "medium"
|
|
|
|
|
| "high"
|
|
|
|
|
}
|
|
|
|
|
} else if (reasoningEffort || reasoningSummary) {
|
|
|
|
|
// Non-reasoning models: only apply if explicitly configured
|
|
|
|
|
options.openai = {}
|
|
|
|
|
if (reasoningEffort) {
|
|
|
|
|
options.openai.reasoningEffort = reasoningEffort as
|
|
|
|
|
| "minimal"
|
|
|
|
|
| "low"
|
|
|
|
|
| "medium"
|
|
|
|
|
| "high"
|
|
|
|
|
}
|
|
|
|
|
if (reasoningSummary) {
|
|
|
|
|
options.openai.reasoningSummary = reasoningSummary as
|
2025-12-23 13:38:50 +09:00
|
|
|
| "auto"
|
2025-12-10 20:54:43 +05:30
|
|
|
| "detailed"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "anthropic": {
|
|
|
|
|
const thinkingBudget = parseIntSafe(
|
|
|
|
|
process.env.ANTHROPIC_THINKING_BUDGET_TOKENS,
|
|
|
|
|
"ANTHROPIC_THINKING_BUDGET_TOKENS",
|
|
|
|
|
1024,
|
|
|
|
|
64000,
|
|
|
|
|
)
|
|
|
|
|
const thinkingType =
|
|
|
|
|
process.env.ANTHROPIC_THINKING_TYPE || "enabled"
|
|
|
|
|
|
|
|
|
|
if (thinkingBudget) {
|
|
|
|
|
options.anthropic = {
|
|
|
|
|
thinking: {
|
|
|
|
|
type: thinkingType,
|
|
|
|
|
budgetTokens: thinkingBudget,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "google": {
|
|
|
|
|
const reasoningEffort = process.env.GOOGLE_REASONING_EFFORT
|
|
|
|
|
const thinkingBudgetVal = parseIntSafe(
|
|
|
|
|
process.env.GOOGLE_THINKING_BUDGET,
|
|
|
|
|
"GOOGLE_THINKING_BUDGET",
|
|
|
|
|
1024,
|
|
|
|
|
100000,
|
|
|
|
|
)
|
|
|
|
|
const thinkingLevel = process.env.GOOGLE_THINKING_LEVEL
|
|
|
|
|
|
|
|
|
|
// Google Gemini 2.5/3 models think by default, but need includeThoughts: true
|
|
|
|
|
// to return the reasoning in the response
|
|
|
|
|
if (
|
|
|
|
|
modelId &&
|
|
|
|
|
(modelId.includes("gemini-2") ||
|
|
|
|
|
modelId.includes("gemini-3") ||
|
|
|
|
|
modelId.includes("gemini2") ||
|
|
|
|
|
modelId.includes("gemini3"))
|
|
|
|
|
) {
|
|
|
|
|
const thinkingConfig: Record<string, any> = {
|
|
|
|
|
includeThoughts: true,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Optionally configure thinking budget or level
|
|
|
|
|
if (
|
|
|
|
|
thinkingBudgetVal &&
|
|
|
|
|
(modelId.includes("2.5") || modelId.includes("2-5"))
|
|
|
|
|
) {
|
|
|
|
|
thinkingConfig.thinkingBudget = thinkingBudgetVal
|
|
|
|
|
} else if (
|
|
|
|
|
thinkingLevel &&
|
|
|
|
|
(modelId.includes("gemini-3") ||
|
|
|
|
|
modelId.includes("gemini3"))
|
|
|
|
|
) {
|
|
|
|
|
thinkingConfig.thinkingLevel = thinkingLevel as
|
|
|
|
|
| "low"
|
|
|
|
|
| "high"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
options.google = { thinkingConfig }
|
|
|
|
|
} else if (reasoningEffort) {
|
|
|
|
|
options.google = {
|
|
|
|
|
reasoningEffort: reasoningEffort as
|
|
|
|
|
| "low"
|
|
|
|
|
| "medium"
|
|
|
|
|
| "high",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Keep existing Google options
|
|
|
|
|
const options_obj: Record<string, any> = {}
|
|
|
|
|
const candidateCount = parseIntSafe(
|
|
|
|
|
process.env.GOOGLE_CANDIDATE_COUNT,
|
|
|
|
|
"GOOGLE_CANDIDATE_COUNT",
|
|
|
|
|
1,
|
|
|
|
|
8,
|
|
|
|
|
)
|
|
|
|
|
if (candidateCount) {
|
|
|
|
|
options_obj.candidateCount = candidateCount
|
|
|
|
|
}
|
|
|
|
|
const topK = parseIntSafe(
|
|
|
|
|
process.env.GOOGLE_TOP_K,
|
|
|
|
|
"GOOGLE_TOP_K",
|
|
|
|
|
1,
|
|
|
|
|
100,
|
|
|
|
|
)
|
|
|
|
|
if (topK) {
|
|
|
|
|
options_obj.topK = topK
|
|
|
|
|
}
|
|
|
|
|
if (process.env.GOOGLE_TOP_P) {
|
|
|
|
|
const topP = Number.parseFloat(process.env.GOOGLE_TOP_P)
|
|
|
|
|
if (Number.isNaN(topP) || topP < 0 || topP > 1) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`GOOGLE_TOP_P must be a number between 0 and 1, got: ${process.env.GOOGLE_TOP_P}`,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
options_obj.topP = topP
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Object.keys(options_obj).length > 0) {
|
|
|
|
|
options.google = { ...options.google, ...options_obj }
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
2026-01-11 00:11:30 -05:00
|
|
|
case "vertexai": {
|
2026-01-14 04:00:05 -05:00
|
|
|
const thinkingBudget = parseIntSafe(
|
|
|
|
|
process.env.GOOGLE_VERTEX_THINKING_BUDGET,
|
|
|
|
|
"GOOGLE_VERTEX_THINKING_BUDGET",
|
|
|
|
|
1024,
|
|
|
|
|
100000,
|
|
|
|
|
)
|
|
|
|
|
const thinkingLevel = process.env.GOOGLE_VERTEX_THINKING_LEVEL
|
2025-12-10 20:54:43 +05:30
|
|
|
|
2026-01-14 04:00:05 -05:00
|
|
|
if (
|
|
|
|
|
modelId &&
|
|
|
|
|
(modelId.includes("gemini-2") ||
|
|
|
|
|
modelId.includes("gemini-3") ||
|
|
|
|
|
modelId.includes("gemini2") ||
|
|
|
|
|
modelId.includes("gemini3"))
|
|
|
|
|
) {
|
2026-01-11 00:11:30 -05:00
|
|
|
const thinkingConfig: Record<string, any> = {
|
|
|
|
|
includeThoughts: true,
|
|
|
|
|
}
|
2026-01-14 03:03:30 -05:00
|
|
|
|
2026-01-11 04:19:57 -05:00
|
|
|
const isGemini3 =
|
2026-01-14 03:03:30 -05:00
|
|
|
modelId?.includes("gemini-3") ||
|
|
|
|
|
modelId?.includes("gemini3")
|
2026-01-14 04:00:05 -05:00
|
|
|
const isGemini25 =
|
|
|
|
|
modelId?.includes("2.5") || modelId?.includes("2-5")
|
|
|
|
|
|
2026-01-11 04:19:57 -05:00
|
|
|
if (isGemini3 && thinkingLevel) {
|
2026-01-14 04:00:05 -05:00
|
|
|
// Vertex AI provider in AI SDK supports more granular levels (minimal/low/medium/high)
|
2026-01-11 00:11:30 -05:00
|
|
|
thinkingConfig.thinkingLevel = thinkingLevel as
|
2026-01-14 03:03:30 -05:00
|
|
|
| "minimal"
|
2026-01-11 00:11:30 -05:00
|
|
|
| "low"
|
2026-01-14 03:03:30 -05:00
|
|
|
| "medium"
|
2026-01-11 00:11:30 -05:00
|
|
|
| "high"
|
2026-01-14 04:00:05 -05:00
|
|
|
} else if (isGemini25 && thinkingBudget) {
|
2026-01-11 04:19:57 -05:00
|
|
|
thinkingConfig.thinkingBudget = thinkingBudget
|
2026-01-11 00:11:30 -05:00
|
|
|
}
|
|
|
|
|
options.google = { thinkingConfig }
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
2025-12-10 20:54:43 +05:30
|
|
|
case "azure": {
|
|
|
|
|
const reasoningEffort = process.env.AZURE_REASONING_EFFORT
|
|
|
|
|
const reasoningSummary = process.env.AZURE_REASONING_SUMMARY
|
|
|
|
|
|
|
|
|
|
if (reasoningEffort || reasoningSummary) {
|
|
|
|
|
options.azure = {}
|
|
|
|
|
if (reasoningEffort) {
|
|
|
|
|
options.azure.reasoningEffort = reasoningEffort as
|
|
|
|
|
| "low"
|
|
|
|
|
| "medium"
|
|
|
|
|
| "high"
|
|
|
|
|
}
|
|
|
|
|
if (reasoningSummary) {
|
|
|
|
|
options.azure.reasoningSummary = reasoningSummary as
|
|
|
|
|
| "none"
|
|
|
|
|
| "brief"
|
|
|
|
|
| "detailed"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "bedrock": {
|
|
|
|
|
const budgetTokens = parseIntSafe(
|
|
|
|
|
process.env.BEDROCK_REASONING_BUDGET_TOKENS,
|
|
|
|
|
"BEDROCK_REASONING_BUDGET_TOKENS",
|
|
|
|
|
1024,
|
|
|
|
|
64000,
|
|
|
|
|
)
|
|
|
|
|
const reasoningEffort = process.env.BEDROCK_REASONING_EFFORT
|
|
|
|
|
|
|
|
|
|
// Bedrock reasoning ONLY for Claude and Nova models
|
|
|
|
|
// Other models (MiniMax, etc.) don't support reasoningConfig
|
|
|
|
|
if (
|
|
|
|
|
modelId &&
|
|
|
|
|
(budgetTokens || reasoningEffort) &&
|
|
|
|
|
(modelId.includes("claude") ||
|
|
|
|
|
modelId.includes("anthropic") ||
|
|
|
|
|
modelId.includes("nova") ||
|
|
|
|
|
modelId.includes("amazon"))
|
|
|
|
|
) {
|
|
|
|
|
const reasoningConfig: Record<string, any> = { type: "enabled" }
|
|
|
|
|
|
|
|
|
|
// Claude models: use budgetTokens (1024-64000)
|
|
|
|
|
if (
|
|
|
|
|
budgetTokens &&
|
|
|
|
|
(modelId.includes("claude") ||
|
|
|
|
|
modelId.includes("anthropic"))
|
|
|
|
|
) {
|
|
|
|
|
reasoningConfig.budgetTokens = budgetTokens
|
|
|
|
|
}
|
|
|
|
|
// Nova models: use maxReasoningEffort (low/medium/high)
|
|
|
|
|
else if (
|
|
|
|
|
reasoningEffort &&
|
|
|
|
|
(modelId.includes("nova") || modelId.includes("amazon"))
|
|
|
|
|
) {
|
|
|
|
|
reasoningConfig.maxReasoningEffort = reasoningEffort as
|
|
|
|
|
| "low"
|
|
|
|
|
| "medium"
|
|
|
|
|
| "high"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
options.bedrock = { reasoningConfig }
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "ollama": {
|
|
|
|
|
const enableThinking = process.env.OLLAMA_ENABLE_THINKING
|
|
|
|
|
// Ollama supports reasoning with think: true for models like qwen3
|
|
|
|
|
if (enableThinking === "true") {
|
|
|
|
|
options.ollama = { think: true }
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "deepseek":
|
|
|
|
|
case "openrouter":
|
2025-12-16 22:43:33 -05:00
|
|
|
case "siliconflow":
|
2025-12-22 22:13:45 +08:00
|
|
|
case "sglang":
|
2025-12-29 11:30:58 +09:00
|
|
|
case "gateway":
|
2026-01-06 18:41:25 +08:00
|
|
|
case "modelscope":
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
case "doubao":
|
|
|
|
|
case "minimax":
|
|
|
|
|
case "glm":
|
|
|
|
|
case "qwen":
|
|
|
|
|
case "kimi":
|
2026-03-24 19:48:34 +08:00
|
|
|
case "qiniu":
|
|
|
|
|
case "novita": {
|
2025-12-10 20:54:43 +05:30
|
|
|
// These providers don't have reasoning configs in AI SDK yet
|
2025-12-16 22:43:33 -05:00
|
|
|
// Gateway passes through to underlying providers which handle their own configs
|
2025-12-10 20:54:43 +05:30
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Object.keys(options).length > 0 ? options : undefined
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-04 14:13:10 +09:00
|
|
|
// Map of provider to required environment variable
|
feat: add file-based admin settings panel at /admin (#866)
* feat: add file-based admin settings panel at /admin
Settings saved in the panel are written to data/settings.json and
overlaid onto process.env, taking precedence over environment
variables and applying immediately without restart. Enable by setting
ADMIN_PASSWORD; on serverless platforms without persistent disk the
panel degrades to read-only.
* polish: admin panel UI improvements
- Provider logos in credential rows (shared ProviderLogo component,
extracted from model-config-dialog)
- Scroll-spy active state in the sidebar nav
- Green success state in the save bar that clears after a few seconds
- Wider content column (max-w-6xl) for less wasted space on desktop
* polish: admin panel section toggles and reorder
- Move Quota & Rate Limits to the end of the settings page
- Add enable switches to Observability and Quota sections; default off
with fields grayed out, auto-on when any field is already configured
* polish: make section enable switch more visible
Wrap the switch in a labeled pill ('Enabled'/'Disabled') with border
and background so the off state is clearly visible.
* refactor: derive admin registry from PROVIDER_INFO, simplify page state
- Provider options, labels, and base-URL placeholders now come from
PROVIDER_INFO instead of hand-copied lists (fixes SiliconFlow .com/.cn
placeholder drift; panel names now match the model-config dialog)
- Replace free-text subgroup strings + SUBGROUP_PROVIDERS reverse map
with a typed provider field on SettingDef
- Precompute SETTINGS_BY_GROUP and PROVIDER_SUBGROUPS at module level
- Merge justSaved into saveMessage, drop unused mainRef, hoist
fetchSettings out of the component, dedupe savedText logic
- Serialize from SETTINGS_REGISTRY directly; json validators in a map
instead of a hardcoded key check
- Make allowPrivateUrls a function so ALLOW_PRIVATE_URLS edits in the
admin panel apply without restart
* feat: graphical model management in admin panel
Replace the provider credential fields and raw AI_MODELS_CONFIG JSON
textarea with a Models section mirroring the in-app model settings UI:
provider instance list with logos, credential fields per provider type,
model add/remove with suggestions, per-model connectivity test, and a
default-provider star.
On save the server derives everything the runtime needs into
settings.json: credential env vars (with _2 suffixes for multiple
instances of one provider), AI_MODELS_CONFIG, and AI_PROVIDER/AI_MODEL
for the default. Secrets round-trip as masked markers and are never
sent back to the browser. The general settings registry now only
covers non-provider settings (generation, access, features,
observability, quota).
* fix: allow testing unsaved providers in admin panel
The test button previously looked up credentials by providerId in the
saved settings, so testing a newly added (unsaved) provider failed with
'Unknown provider or model'. The test endpoint now accepts the client's
current provider state; newly typed secrets are used as-is and masked
markers are resolved against the stored values, so testing works both
before and after saving.
* fix: merge env AI_MODELS_CONFIG with admin panel providers
Previously, saving in the admin panel wrote a complete AI_MODELS_CONFIG
into settings.json, which (by overlay precedence) replaced any config
from .env or ai-models.json — admins lost their env-configured models.
The panel no longer writes AI_MODELS_CONFIG. Instead its providers are
merged with the env baseline at read time in loadRawServerModelsConfig,
and panel credentials go to ADMIN_-prefixed env vars wired up via
apiKeyEnv/baseUrlEnv so they never shadow standard vars. Env-based
providers now appear read-only in the panel, name clashes are rejected,
and a panel default overrides the env default. data/ is now gitignored.
* fix: block global-credential providers already managed via env
Bedrock, Vertex AI, and Ollama credentials live in fixed env vars with
no apiKeyEnv redirection, so a panel instance of one of these would
silently override the credentials that env-configured models rely on.
The API now rejects saving such a provider when the env config already
uses that type, and the Add Provider dropdown disables it with a
'managed via env' note.
* fix: address admin panel review findings
- Security: test-model no longer resolves a stored secret when the
request's baseUrl/provider differs from the stored entry, closing a
path where a tampered baseUrl could exfiltrate a saved key
- Save failures are now visible: the save bar shows the error in red
(was masked by the persistent 'Unsaved changes' text), and per-field
validation errors from the settings API are surfaced under each field
- The Observability/Quota enable switch is now real: toggling off stages
deletion of the group's saved values, and the toggle no longer snaps
back to Enabled after saving
- Env provider's default star is hidden when a panel provider is the
active default (no more double star)
- Clearing a credential field reverts to the stored value instead of
silently deleting it; an explicit X button removes a stored secret
- Form inputs are disabled during an in-flight save
* refactor(admin): split 1549-line admin page into focused modules
Extract admin-shared.ts (types + fetch helper), setting-field.tsx
(registry-driven fields), and models-section.tsx (provider/model
manager) from page.tsx. Pure mechanical move, no behavior change.
* feat(admin): share credential fields with user dialog and localize panel
Extract ProviderCredentialsFields (display name + per-provider
credential inputs) used by both the user ModelConfigDialog and the
admin Models panel; secret input passed via renderSecret (plaintext
vs masked), test button via footer slot. Add full i18n for the admin
panel across en/zh/ja/zh-Hant, reusing modelConfig.* for shared parts.
* fix(admin): address Copilot review findings
- Reflect built-in defaults for boolean settings (ALLOW_PRIVATE_URLS
defaults on) and allow clearing a saved boolean back to default,
so the SSRF toggle matches actual runtime behavior.
- Harden JSON loading: filter settings values to strings only, and
schema-validate stored ADMIN_PROVIDERS entries, dropping malformed
ones instead of letting them reach runtime code.
- Set beforeunload returnValue so the unsaved-changes prompt shows in
all browsers; reject non-finite numbers in settings validation.
- Fix README/CN/JA docs that claimed the panel auto-generates
AI_MODELS_CONFIG (providers are merged at read time, not written).
- Add unit tests for corrupted-file value filtering and provider
schema validation.
* docs: move admin panel details to dedicated docs/{en,cn,ja}/admin-panel.md
The READMEs now carry a short blurb + link, matching the existing
per-topic docs (docker.md, ai-providers.md, ...). Removes the ~22-line
inline section and the duplicated data/settings.json mentions.
* fix(admin): address follow-up Copilot findings on the prior fixes
- loadAdminProviders now validates against a stored-shape schema where
secrets are plain strings, so a hand-edited ADMIN_PROVIDERS holding an
{isSet} marker is dropped instead of later crashing maskSecret().
- loadSettings guards against array values (typeof [] === 'object'),
which would otherwise overlay numeric keys onto process.env.
- Admin SecretInput uses the bare id so the shared component's
<Label htmlFor> stays associated (only one ProviderDetail mounts).
- Add tests: marker-secret rejection, array-values guard, bedrock
multi-secret round-trip.
2026-06-15 00:40:35 +09:00
|
|
|
export const PROVIDER_ENV_VARS: Record<ProviderName, string | null> = {
|
2025-12-06 12:46:40 +09:00
|
|
|
bedrock: null, // AWS SDK auto-uses IAM role on AWS, or env vars locally
|
|
|
|
|
openai: "OPENAI_API_KEY",
|
|
|
|
|
anthropic: "ANTHROPIC_API_KEY",
|
|
|
|
|
google: "GOOGLE_GENERATIVE_AI_API_KEY",
|
2026-01-14 03:03:30 -05:00
|
|
|
vertexai: "GOOGLE_VERTEX_API_KEY",
|
2025-12-06 12:46:40 +09:00
|
|
|
azure: "AZURE_API_KEY",
|
|
|
|
|
ollama: null, // No credentials needed for local Ollama
|
|
|
|
|
openrouter: "OPENROUTER_API_KEY",
|
|
|
|
|
deepseek: "DEEPSEEK_API_KEY",
|
2025-12-07 09:22:57 +08:00
|
|
|
siliconflow: "SILICONFLOW_API_KEY",
|
2025-12-22 22:13:45 +08:00
|
|
|
sglang: "SGLANG_API_KEY",
|
2025-12-16 22:43:33 -05:00
|
|
|
gateway: "AI_GATEWAY_API_KEY",
|
2025-12-30 21:13:22 +08:00
|
|
|
edgeone: null, // No credentials needed - uses EdgeOne Edge AI
|
2025-12-29 11:30:58 +09:00
|
|
|
doubao: "DOUBAO_API_KEY",
|
2026-01-06 18:41:25 +08:00
|
|
|
modelscope: "MODELSCOPE_API_KEY",
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
glm: "GLM_API_KEY",
|
|
|
|
|
qwen: "QWEN_API_KEY",
|
|
|
|
|
qiniu: "QINIU_API_KEY",
|
|
|
|
|
kimi: "KIMI_API_KEY",
|
|
|
|
|
minimax: "MINIMAX_API_KEY",
|
2026-03-24 19:48:34 +08:00
|
|
|
novita: "NOVITA_API_KEY",
|
2025-12-06 12:46:40 +09:00
|
|
|
}
|
2025-12-04 14:13:10 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Auto-detect provider based on available API keys
|
|
|
|
|
* Returns the provider if exactly one is configured, otherwise null
|
|
|
|
|
*/
|
|
|
|
|
function detectProvider(): ProviderName | null {
|
2025-12-06 12:46:40 +09:00
|
|
|
const configuredProviders: ProviderName[] = []
|
2025-12-04 14:13:10 +09:00
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
for (const [provider, envVar] of Object.entries(PROVIDER_ENV_VARS)) {
|
|
|
|
|
if (envVar === null) {
|
|
|
|
|
// Skip ollama - it doesn't require credentials
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-06-02 18:26:27 +08:00
|
|
|
// Anthropic accepts ANTHROPIC_AUTH_TOKEN (Bearer auth) as alternative to ANTHROPIC_API_KEY
|
|
|
|
|
const hasCredential =
|
|
|
|
|
provider === "anthropic"
|
|
|
|
|
? !!(
|
|
|
|
|
process.env.ANTHROPIC_API_KEY ||
|
|
|
|
|
process.env.ANTHROPIC_AUTH_TOKEN
|
|
|
|
|
)
|
|
|
|
|
: !!process.env[envVar]
|
|
|
|
|
if (hasCredential) {
|
2025-12-11 21:49:50 +09:00
|
|
|
// Azure requires additional config (baseURL or resourceName)
|
|
|
|
|
if (provider === "azure") {
|
|
|
|
|
const hasBaseUrl = !!process.env.AZURE_BASE_URL
|
|
|
|
|
const hasResourceName = !!process.env.AZURE_RESOURCE_NAME
|
|
|
|
|
if (hasBaseUrl || hasResourceName) {
|
|
|
|
|
configuredProviders.push(provider as ProviderName)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
configuredProviders.push(provider as ProviderName)
|
|
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
}
|
2025-12-04 14:13:10 +09:00
|
|
|
}
|
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
if (configuredProviders.length === 1) {
|
|
|
|
|
return configuredProviders[0]
|
|
|
|
|
}
|
2025-12-04 14:13:10 +09:00
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
return null
|
2025-12-04 14:13:10 +09:00
|
|
|
}
|
|
|
|
|
|
2025-11-15 13:36:42 +09:00
|
|
|
/**
|
|
|
|
|
* Validate that required API keys are present for the selected provider
|
2026-01-15 21:28:22 +05:30
|
|
|
* @param provider - The provider to validate
|
2026-02-02 15:54:35 +09:00
|
|
|
* @param customApiKeyEnv - Optional custom env var name(s) (from ai-models.json apiKeyEnv)
|
2025-11-15 13:36:42 +09:00
|
|
|
*/
|
2026-01-15 21:28:22 +05:30
|
|
|
function validateProviderCredentials(
|
|
|
|
|
provider: ProviderName,
|
2026-02-02 15:54:35 +09:00
|
|
|
customApiKeyEnv?: string | string[],
|
2026-01-15 21:28:22 +05:30
|
|
|
): void {
|
2026-02-02 15:54:35 +09:00
|
|
|
// Handle array of env var names - at least one must be set
|
|
|
|
|
if (Array.isArray(customApiKeyEnv)) {
|
|
|
|
|
const hasAnyKey = customApiKeyEnv.some((envVar) => process.env[envVar])
|
|
|
|
|
if (!hasAnyKey) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`At least one of [${customApiKeyEnv.join(", ")}] environment variables is required for ${provider} provider. ` +
|
|
|
|
|
`Please set at least one in your .env.local file.`,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 18:26:27 +08:00
|
|
|
// Anthropic accepts ANTHROPIC_AUTH_TOKEN (Bearer auth) as alternative to ANTHROPIC_API_KEY
|
|
|
|
|
if (provider === "anthropic" && !customApiKeyEnv) {
|
|
|
|
|
const hasCredential = !!(
|
|
|
|
|
process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN
|
2025-12-06 12:46:40 +09:00
|
|
|
)
|
2026-06-02 18:26:27 +08:00
|
|
|
if (!hasCredential) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN environment variable is required for anthropic provider. ` +
|
|
|
|
|
`Please set one in your .env.local file.`,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Use custom env var name if provided, otherwise use default
|
|
|
|
|
const requiredVar = customApiKeyEnv || PROVIDER_ENV_VARS[provider]
|
|
|
|
|
if (requiredVar && !process.env[requiredVar]) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`${requiredVar} environment variable is required for ${provider} provider. ` +
|
|
|
|
|
`Please set it in your .env.local file.`,
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
}
|
2025-12-11 21:49:50 +09:00
|
|
|
|
|
|
|
|
// Azure requires either AZURE_BASE_URL or AZURE_RESOURCE_NAME in addition to API key
|
|
|
|
|
if (provider === "azure") {
|
|
|
|
|
const hasBaseUrl = !!process.env.AZURE_BASE_URL
|
|
|
|
|
const hasResourceName = !!process.env.AZURE_RESOURCE_NAME
|
|
|
|
|
if (!hasBaseUrl && !hasResourceName) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Azure requires either AZURE_BASE_URL or AZURE_RESOURCE_NAME to be set. ` +
|
|
|
|
|
`Please set one in your .env.local file.`,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-15 13:36:42 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the AI model based on environment variables
|
|
|
|
|
*
|
|
|
|
|
* Environment variables:
|
2026-01-06 18:41:25 +08:00
|
|
|
* - AI_PROVIDER: The provider to use (bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow, sglang, gateway, modelscope)
|
2025-11-15 13:36:42 +09:00
|
|
|
* - AI_MODEL: The model ID/name for the selected provider
|
|
|
|
|
*
|
|
|
|
|
* Provider-specific env vars:
|
|
|
|
|
* - OPENAI_API_KEY: OpenAI API key
|
2025-11-21 16:58:42 +08:00
|
|
|
* - OPENAI_BASE_URL: Custom OpenAI-compatible endpoint (optional)
|
2025-11-15 13:36:42 +09:00
|
|
|
* - ANTHROPIC_API_KEY: Anthropic API key
|
|
|
|
|
* - GOOGLE_GENERATIVE_AI_API_KEY: Google API key
|
|
|
|
|
* - AZURE_RESOURCE_NAME, AZURE_API_KEY: Azure OpenAI credentials
|
|
|
|
|
* - AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY: AWS Bedrock credentials
|
2026-02-26 13:55:21 +01:00
|
|
|
* - OLLAMA_BASE_URL: Ollama server URL (optional, defaults to https://ollama.com/api)
|
2025-11-15 14:29:18 +09:00
|
|
|
* - OPENROUTER_API_KEY: OpenRouter API key
|
2025-12-02 11:52:09 +09:00
|
|
|
* - DEEPSEEK_API_KEY: DeepSeek API key
|
|
|
|
|
* - DEEPSEEK_BASE_URL: DeepSeek endpoint (optional)
|
2025-12-07 09:22:57 +08:00
|
|
|
* - SILICONFLOW_API_KEY: SiliconFlow API key
|
2026-01-09 13:21:34 +09:00
|
|
|
* - SILICONFLOW_BASE_URL: SiliconFlow endpoint (optional, defaults to https://api.siliconflow.cn/v1)
|
2025-12-22 22:13:45 +08:00
|
|
|
* - SGLANG_API_KEY: SGLang API key
|
|
|
|
|
* - SGLANG_BASE_URL: SGLang endpoint (optional)
|
2026-01-06 18:41:25 +08:00
|
|
|
* - MODELSCOPE_API_KEY: ModelScope API key
|
|
|
|
|
* - MODELSCOPE_BASE_URL: ModelScope endpoint (optional)
|
2025-11-15 13:36:42 +09:00
|
|
|
*/
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
export function getAIModel(overrides?: ClientOverrides): ModelConfig {
|
2025-12-15 15:02:18 +09:00
|
|
|
// SECURITY: Prevent SSRF attacks (GHSA-9qf7-mprq-9qgm)
|
|
|
|
|
// If a custom baseUrl is provided, an API key MUST also be provided.
|
|
|
|
|
// This prevents attackers from redirecting server API keys to malicious endpoints.
|
2026-02-26 13:55:21 +01:00
|
|
|
// Exception: EdgeOne doesn't require API keys.
|
|
|
|
|
// Ollama is exempt only when no server OLLAMA_API_KEY is configured;
|
|
|
|
|
// when it IS configured, the outer guard also enforces client apiKey for custom baseUrls.
|
2025-12-30 21:13:22 +08:00
|
|
|
if (
|
|
|
|
|
overrides?.baseUrl &&
|
|
|
|
|
!overrides?.apiKey &&
|
2026-01-14 04:00:05 -05:00
|
|
|
!(overrides?.provider === "vertexai" && overrides?.vertexApiKey) &&
|
2026-01-29 21:24:09 +09:00
|
|
|
overrides?.provider !== "edgeone" &&
|
2026-02-26 13:55:21 +01:00
|
|
|
!(overrides?.provider === "ollama" && !process.env.OLLAMA_API_KEY)
|
2025-12-30 21:13:22 +08:00
|
|
|
) {
|
2025-12-15 15:02:18 +09:00
|
|
|
throw new Error(
|
|
|
|
|
`API key is required when using a custom base URL. ` +
|
|
|
|
|
`Please provide your own API key in Settings.`,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
// Check if client is providing their own provider override
|
2026-01-14 03:03:30 -05:00
|
|
|
const isClientOverride = !!(
|
|
|
|
|
overrides?.provider &&
|
|
|
|
|
(overrides?.apiKey ||
|
|
|
|
|
(overrides?.provider === "vertexai" && overrides?.vertexApiKey))
|
|
|
|
|
)
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
|
|
|
|
|
// Use client override if provided, otherwise fall back to env vars
|
|
|
|
|
const modelId = overrides?.modelId || process.env.AI_MODEL
|
2025-11-15 13:36:42 +09:00
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
if (!modelId) {
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
if (isClientOverride) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Model ID is required when using custom AI provider. Please specify a model in Settings.`,
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
throw new Error(
|
|
|
|
|
`AI_MODEL environment variable is required. Example: AI_MODEL=claude-sonnet-4-5`,
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-11-15 13:36:42 +09:00
|
|
|
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
// Determine provider: client override > explicit config > auto-detect > error
|
2025-12-06 12:46:40 +09:00
|
|
|
let provider: ProviderName
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
if (overrides?.provider) {
|
|
|
|
|
// Validate client-provided provider
|
|
|
|
|
if (
|
|
|
|
|
!ALLOWED_CLIENT_PROVIDERS.includes(
|
|
|
|
|
overrides.provider as ProviderName,
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Invalid provider: ${overrides.provider}. Allowed providers: ${ALLOWED_CLIENT_PROVIDERS.join(", ")}`,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
provider = overrides.provider as ProviderName
|
|
|
|
|
} else if (process.env.AI_PROVIDER) {
|
2025-12-06 12:46:40 +09:00
|
|
|
provider = process.env.AI_PROVIDER as ProviderName
|
2025-12-04 14:13:10 +09:00
|
|
|
} else {
|
2025-12-06 12:46:40 +09:00
|
|
|
const detected = detectProvider()
|
|
|
|
|
if (detected) {
|
|
|
|
|
provider = detected
|
|
|
|
|
console.log(`[AI Provider] Auto-detected provider: ${provider}`)
|
|
|
|
|
} else {
|
|
|
|
|
// List configured providers for better error message
|
|
|
|
|
const configured = Object.entries(PROVIDER_ENV_VARS)
|
|
|
|
|
.filter(([, envVar]) => envVar && process.env[envVar as string])
|
|
|
|
|
.map(([p]) => p)
|
2025-12-04 14:13:10 +09:00
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
if (configured.length === 0) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`No AI provider configured. Please set one of the following API keys in your .env.local file:\n` +
|
2025-12-16 22:43:33 -05:00
|
|
|
`- AI_GATEWAY_API_KEY for Vercel AI Gateway\n` +
|
2025-12-06 12:46:40 +09:00
|
|
|
`- DEEPSEEK_API_KEY for DeepSeek\n` +
|
|
|
|
|
`- OPENAI_API_KEY for OpenAI\n` +
|
|
|
|
|
`- ANTHROPIC_API_KEY for Anthropic\n` +
|
|
|
|
|
`- GOOGLE_GENERATIVE_AI_API_KEY for Google\n` +
|
|
|
|
|
`- AWS_ACCESS_KEY_ID for Bedrock\n` +
|
|
|
|
|
`- OPENROUTER_API_KEY for OpenRouter\n` +
|
|
|
|
|
`- AZURE_API_KEY for Azure\n` +
|
2025-12-07 09:22:57 +08:00
|
|
|
`- SILICONFLOW_API_KEY for SiliconFlow\n` +
|
2025-12-22 22:13:45 +08:00
|
|
|
`- SGLANG_API_KEY for SGLang\n` +
|
2026-01-06 18:41:25 +08:00
|
|
|
`- MODELSCOPE_API_KEY for ModelScope\n` +
|
2025-12-06 12:46:40 +09:00
|
|
|
`Or set AI_PROVIDER=ollama for local Ollama.`,
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Multiple AI providers configured (${configured.join(", ")}). ` +
|
|
|
|
|
`Please set AI_PROVIDER to specify which one to use.`,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-04 14:13:10 +09:00
|
|
|
}
|
|
|
|
|
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
// Only validate server credentials if client isn't providing their own API key
|
|
|
|
|
if (!isClientOverride) {
|
2026-01-15 21:28:22 +05:30
|
|
|
validateProviderCredentials(provider, overrides?.apiKeyEnv)
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
}
|
2025-11-15 13:36:42 +09:00
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
console.log(`[AI Provider] Initializing ${provider} with model: ${modelId}`)
|
2025-11-15 13:36:42 +09:00
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
let model: any
|
|
|
|
|
let providerOptions: any
|
|
|
|
|
let headers: Record<string, string> | undefined
|
2025-11-15 13:36:42 +09:00
|
|
|
|
2025-12-10 20:54:43 +05:30
|
|
|
// Build provider-specific options from environment variables
|
|
|
|
|
const customProviderOptions = buildProviderOptions(provider, modelId)
|
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
switch (provider) {
|
|
|
|
|
case "bedrock": {
|
feat: multi-provider model configuration with UI/UX improvements (#355)
* feat: add multi-provider model configuration
- Add model config dialog for managing multiple AI providers
- Support for OpenAI, Anthropic, Google, Azure, Bedrock, OpenRouter, DeepSeek, SiliconFlow, Ollama, and AI Gateway
- Add model selector dropdown in chat panel header
- Add API key validation endpoint
- Add custom model ID input with keyboard navigation
- Fix hover highlight in Command component
- Add suggested models for each provider including latest Claude 4.5 series
- Store configuration locally in browser
* feat: improve model config UI and move selector to chat input
- Move model selector from header to chat input (left of send button)
- Add per-model validation status (queued, running, valid, invalid)
- Filter model selector to only show verified models
- Add editable model IDs in config dialog
- Add custom model input field alongside suggested models dropdown
- Fix hover states on provider buttons and select triggers
- Update OpenAI suggested models with GPT-5 series
- Add alert-dialog component for delete confirmation
* refactor: revert shadcn component changes, apply hover fix at usage site
* feat: add AWS credentials support for Bedrock provider
- Add AWS Access Key ID, Secret Access Key, Region fields for Bedrock
- Show different credential fields based on provider type
- Update validation API to handle Bedrock with AWS credentials
- Add region selector with common AWS regions
* fix: reset Test button after validation completes
* fix: reset validation button to Test after success
* fix: complete bedrock support and UI/UX improvements
- Add bedrock to ALLOWED_CLIENT_PROVIDERS for client credentials
- Pass AWS credentials through full chain (headers → API → provider)
- Replace non-existent GPT-5 models with real ones (o1, o3-mini)
- Add accessibility: aria-labels, focus-visible rings, inline errors
- Add more AWS regions (Ohio, London, Paris, Mumbai, Seoul, São Paulo)
- Fix setTimeout cleanup with useRef on component unmount
- Fix TypeScript type consistency in getSelectedAIConfig fallback
* chore: remove unused code
- Remove unused setAccessCodeRequired state in chat-panel.tsx
- Remove unused getSelectedModel export in model-config.ts
* fix: UI/UX improvements for model configuration dialog
- Add gradient header styling with icon badge
- Change Configuration section icon from Key to Settings2
- Add duplicate model detection with warning banner and inline removal
- Filter out already-added models from suggestions dropdown
- Add type-to-confirm for deleting providers with 3+ models
- Enhance delete confirmation dialog with warning icon
- Improve model selector discoverability (show model name + chevron)
- Add truncation for long model names with title tooltip
- Remove AI provider settings from Settings dialog (now in Model Config)
- Extract ValidationButton into reusable component
* fix: prevent duplicate model IDs within same provider
- Block adding model if ID already exists in provider
- Block editing model ID to match existing model in provider
* fix: improve duplicate model ID notifications
- Add toast notification when trying to add duplicate model
- Allow free typing when editing model ID, validate on blur
- Show warning toast instead of blocking input
* fix: improve duplicate model validation UX in config dialog
- Add inline error display for duplicate model IDs
- Show red border on input when error exists
- Validate on blur with shake animation for edit errors
- Prevent saving empty model names
- Clear errors when user starts typing
- Simplify error styling (small red text, no heavy chips)
2025-12-22 22:36:36 +09:00
|
|
|
// Use client-provided credentials if available, otherwise fall back to IAM/env vars
|
|
|
|
|
const hasClientCredentials =
|
|
|
|
|
overrides?.awsAccessKeyId && overrides?.awsSecretAccessKey
|
|
|
|
|
const bedrockRegion =
|
|
|
|
|
overrides?.awsRegion || process.env.AWS_REGION || "us-west-2"
|
|
|
|
|
|
|
|
|
|
const bedrockProvider = hasClientCredentials
|
|
|
|
|
? createAmazonBedrock({
|
|
|
|
|
region: bedrockRegion,
|
test: add Vitest and Playwright testing infrastructure (#512)
* test: add Vitest and Playwright testing infrastructure
- Add Vitest for unit tests (39 tests)
- cached-responses.test.ts
- ai-providers.test.ts
- chat-helpers.test.ts
- utils.test.ts
- Add Playwright for E2E tests (3 smoke tests)
- Homepage load
- Japanese locale
- Settings dialog
- Add CI workflow (.github/workflows/test.yml)
- Add vitest.config.mts and playwright.config.ts
- Update .gitignore for test artifacts
* test: add more E2E tests for UI components
- Chat panel tests (interactive elements, iframe)
- Settings tests (dark mode, language, draw.io theme)
- Save dialog tests (buttons exist)
- History dialog tests
- Model config tests
- Keyboard interaction tests
- Upload area tests
Total: 15 E2E tests, all passing
* test: fix E2E test issues from review
Fixes based on Gemini and Codex review:
- Remove brittle nth(1) selector in keyboard tests
- Remove waitForTimeout(500) race condition
- Remove if(isVisible) silent skip patterns
- Add proper assertions instead of no-op checks
- Remove expect(count >= 0) that always passes
- Remove unused hasProviderUI variable
All 14 E2E tests and 39 unit tests pass.
* style: auto-format with Biome
* fix: resolve lint errors for CI
* test(e2e): add diagram generation tests with mocked AI responses
- Add tests for generate, edit, and append diagram operations
- Use SSE mocked responses matching AI SDK UI message stream format
- Generate mxCell XML directly in tests for deterministic assertions
- Tests verify tool card rendering and 'Complete' badge state
* test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
* refactor: extract shared test helpers and improve error assertions
- Create tests/e2e/lib/helpers.ts with shared SSE mock functions
- Add proper error UI assertions to error-handling.spec.ts
- Remove waitForTimeout calls in favor of real assertions
- Update 6 test files to use shared helpers
* docs: add testing section to CONTRIBUTING.md
* fix: improve test infrastructure based on PR review
- Fix double build in CI: remove redundant build from playwright webServer
- Export chat helpers from shared module for proper unit testing
- Replace waitForTimeout with explicit waits in E2E tests
- Add data-testid attributes to settings and new chat buttons
- Add list reporter for CI to show failures in logs
- Add Playwright browser caching to speed up CI
- Add vitest coverage configuration
- Fix conditional test assertions to use test.skip() instead of silent pass
- Remove unused variables flagged by linter
* fix: improve E2E test assertions and remove silent skips
- Replace silent test.skip() with explicit conditional skips
- Add actual persistence assertion after page reload
- Use data-testid selector for new chat button test
* refactor: add shared fixtures and test.step() patterns
- Add tests/e2e/lib/fixtures.ts with shared test helpers
- Add tests/e2e/fixtures/diagrams.ts with XML test data
- Add expectBeforeAndAfterReload() helper for persistence tests
- Add test.step() for better test reporting in complex tests
- Consolidate mock helpers into fixtures module
- Reduce code duplication across 17 test files
* fix: make persistence tests more reliable
- Remove expectBeforeAndAfterReload from mocked API tests
- Add explicit test.step() for before/after reload checks
- Add retry config for flaky clipboard tests
- Add sleep after reload for language persistence test
* test: remove flaky XML paste test
* docs: run both unit and e2e tests before PR
* chore: add type check and unit test git hooks
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-01-05 01:37:32 +09:00
|
|
|
accessKeyId: overrides.awsAccessKeyId as string,
|
|
|
|
|
secretAccessKey: overrides.awsSecretAccessKey as string,
|
feat: multi-provider model configuration with UI/UX improvements (#355)
* feat: add multi-provider model configuration
- Add model config dialog for managing multiple AI providers
- Support for OpenAI, Anthropic, Google, Azure, Bedrock, OpenRouter, DeepSeek, SiliconFlow, Ollama, and AI Gateway
- Add model selector dropdown in chat panel header
- Add API key validation endpoint
- Add custom model ID input with keyboard navigation
- Fix hover highlight in Command component
- Add suggested models for each provider including latest Claude 4.5 series
- Store configuration locally in browser
* feat: improve model config UI and move selector to chat input
- Move model selector from header to chat input (left of send button)
- Add per-model validation status (queued, running, valid, invalid)
- Filter model selector to only show verified models
- Add editable model IDs in config dialog
- Add custom model input field alongside suggested models dropdown
- Fix hover states on provider buttons and select triggers
- Update OpenAI suggested models with GPT-5 series
- Add alert-dialog component for delete confirmation
* refactor: revert shadcn component changes, apply hover fix at usage site
* feat: add AWS credentials support for Bedrock provider
- Add AWS Access Key ID, Secret Access Key, Region fields for Bedrock
- Show different credential fields based on provider type
- Update validation API to handle Bedrock with AWS credentials
- Add region selector with common AWS regions
* fix: reset Test button after validation completes
* fix: reset validation button to Test after success
* fix: complete bedrock support and UI/UX improvements
- Add bedrock to ALLOWED_CLIENT_PROVIDERS for client credentials
- Pass AWS credentials through full chain (headers → API → provider)
- Replace non-existent GPT-5 models with real ones (o1, o3-mini)
- Add accessibility: aria-labels, focus-visible rings, inline errors
- Add more AWS regions (Ohio, London, Paris, Mumbai, Seoul, São Paulo)
- Fix setTimeout cleanup with useRef on component unmount
- Fix TypeScript type consistency in getSelectedAIConfig fallback
* chore: remove unused code
- Remove unused setAccessCodeRequired state in chat-panel.tsx
- Remove unused getSelectedModel export in model-config.ts
* fix: UI/UX improvements for model configuration dialog
- Add gradient header styling with icon badge
- Change Configuration section icon from Key to Settings2
- Add duplicate model detection with warning banner and inline removal
- Filter out already-added models from suggestions dropdown
- Add type-to-confirm for deleting providers with 3+ models
- Enhance delete confirmation dialog with warning icon
- Improve model selector discoverability (show model name + chevron)
- Add truncation for long model names with title tooltip
- Remove AI provider settings from Settings dialog (now in Model Config)
- Extract ValidationButton into reusable component
* fix: prevent duplicate model IDs within same provider
- Block adding model if ID already exists in provider
- Block editing model ID to match existing model in provider
* fix: improve duplicate model ID notifications
- Add toast notification when trying to add duplicate model
- Allow free typing when editing model ID, validate on blur
- Show warning toast instead of blocking input
* fix: improve duplicate model validation UX in config dialog
- Add inline error display for duplicate model IDs
- Show red border on input when error exists
- Validate on blur with shake animation for edit errors
- Prevent saving empty model names
- Clear errors when user starts typing
- Simplify error styling (small red text, no heavy chips)
2025-12-22 22:36:36 +09:00
|
|
|
...(overrides?.awsSessionToken && {
|
|
|
|
|
sessionToken: overrides.awsSessionToken,
|
|
|
|
|
}),
|
|
|
|
|
})
|
|
|
|
|
: createAmazonBedrock({
|
|
|
|
|
region: bedrockRegion,
|
|
|
|
|
credentialProvider: fromNodeProviderChain(),
|
|
|
|
|
})
|
2025-12-06 12:46:40 +09:00
|
|
|
model = bedrockProvider(modelId)
|
|
|
|
|
// Add Anthropic beta options if using Claude models via Bedrock
|
|
|
|
|
if (modelId.includes("anthropic.claude")) {
|
2025-12-10 20:54:43 +05:30
|
|
|
// Deep merge to preserve both anthropicBeta and reasoningConfig
|
|
|
|
|
providerOptions = {
|
|
|
|
|
bedrock: {
|
|
|
|
|
...BEDROCK_ANTHROPIC_BETA.bedrock,
|
|
|
|
|
...(customProviderOptions?.bedrock || {}),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
} else if (customProviderOptions) {
|
|
|
|
|
providerOptions = customProviderOptions
|
2025-12-06 12:46:40 +09:00
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
2025-11-15 13:36:42 +09:00
|
|
|
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
case "openai": {
|
2026-01-15 21:28:22 +05:30
|
|
|
const apiKey = resolveApiKey(overrides, "OPENAI_API_KEY")
|
|
|
|
|
const serverBaseUrl = resolveBaseUrlEnv(
|
|
|
|
|
overrides,
|
|
|
|
|
"OPENAI_BASE_URL",
|
|
|
|
|
)
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
2026-01-15 21:28:22 +05:30
|
|
|
serverBaseUrl,
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
)
|
2025-12-23 20:29:48 +09:00
|
|
|
if (baseURL) {
|
|
|
|
|
// Custom base URL = third-party proxy, use Chat Completions API
|
|
|
|
|
// for compatibility (most proxies don't support /responses endpoint)
|
|
|
|
|
const customOpenAI = createOpenAI({ apiKey, baseURL })
|
|
|
|
|
model = customOpenAI.chat(modelId)
|
|
|
|
|
} else if (overrides?.apiKey) {
|
|
|
|
|
// Custom API key but official OpenAI endpoint, use Responses API
|
|
|
|
|
// to support reasoning for gpt-5, o1, o3, o4 models
|
|
|
|
|
const customOpenAI = createOpenAI({ apiKey })
|
2025-12-23 13:38:50 +09:00
|
|
|
model = customOpenAI(modelId)
|
2025-12-06 12:46:40 +09:00
|
|
|
} else {
|
|
|
|
|
model = openai(modelId)
|
|
|
|
|
}
|
|
|
|
|
break
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
}
|
2025-11-15 13:36:42 +09:00
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
case "anthropic": {
|
2026-01-15 21:28:22 +05:30
|
|
|
const apiKey = resolveApiKey(overrides, "ANTHROPIC_API_KEY")
|
|
|
|
|
const serverBaseUrl = resolveBaseUrlEnv(
|
|
|
|
|
overrides,
|
|
|
|
|
"ANTHROPIC_BASE_URL",
|
|
|
|
|
)
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
2026-01-15 21:28:22 +05:30
|
|
|
serverBaseUrl,
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
"https://api.anthropic.com/v1",
|
|
|
|
|
)
|
2026-06-02 18:26:27 +08:00
|
|
|
// Anthropic supports two auth methods (mutually exclusive):
|
|
|
|
|
// - apiKey: sends as `x-api-key` header
|
|
|
|
|
// - authToken: sends as `Authorization: Bearer <token>` header
|
|
|
|
|
// Prefer apiKey if present (including client overrides); fall back
|
|
|
|
|
// to ANTHROPIC_AUTH_TOKEN env var only when no apiKey is available.
|
|
|
|
|
const authToken = !apiKey
|
|
|
|
|
? process.env.ANTHROPIC_AUTH_TOKEN
|
|
|
|
|
: undefined
|
2025-12-06 12:46:40 +09:00
|
|
|
const customProvider = createAnthropic({
|
2026-06-02 18:26:27 +08:00
|
|
|
...(authToken ? { authToken } : { apiKey }),
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
baseURL,
|
2025-12-06 12:46:40 +09:00
|
|
|
headers: ANTHROPIC_BETA_HEADERS,
|
|
|
|
|
})
|
|
|
|
|
model = customProvider(modelId)
|
|
|
|
|
// Add beta headers for fine-grained tool streaming
|
|
|
|
|
headers = ANTHROPIC_BETA_HEADERS
|
|
|
|
|
break
|
|
|
|
|
}
|
2025-11-15 13:36:42 +09:00
|
|
|
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
case "google": {
|
2026-01-15 21:28:22 +05:30
|
|
|
const apiKey = resolveApiKey(
|
|
|
|
|
overrides,
|
|
|
|
|
"GOOGLE_GENERATIVE_AI_API_KEY",
|
|
|
|
|
)
|
|
|
|
|
const serverBaseUrl = resolveBaseUrlEnv(
|
|
|
|
|
overrides,
|
|
|
|
|
"GOOGLE_BASE_URL",
|
|
|
|
|
)
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
2026-01-15 21:28:22 +05:30
|
|
|
serverBaseUrl,
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
)
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
if (baseURL || overrides?.apiKey) {
|
2025-12-06 12:46:40 +09:00
|
|
|
const customGoogle = createGoogleGenerativeAI({
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
apiKey,
|
|
|
|
|
...(baseURL && { baseURL }),
|
2025-12-06 12:46:40 +09:00
|
|
|
})
|
|
|
|
|
model = customGoogle(modelId)
|
|
|
|
|
} else {
|
|
|
|
|
model = google(modelId)
|
|
|
|
|
}
|
|
|
|
|
break
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
}
|
2026-01-11 00:11:30 -05:00
|
|
|
case "vertexai": {
|
2026-01-14 03:03:30 -05:00
|
|
|
// Express Mode: Use API key for authentication
|
|
|
|
|
const vertexApiKey =
|
|
|
|
|
overrides?.vertexApiKey || process.env.GOOGLE_VERTEX_API_KEY
|
|
|
|
|
|
|
|
|
|
if (!vertexApiKey) {
|
2026-01-11 00:11:30 -05:00
|
|
|
throw new Error(
|
2026-01-14 03:03:30 -05:00
|
|
|
"Vertex AI requires an API key for Express Mode. " +
|
|
|
|
|
"Get one from Google Cloud Console or set GOOGLE_VERTEX_API_KEY environment variable.",
|
2026-01-11 00:11:30 -05:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-14 03:03:30 -05:00
|
|
|
// Support custom base URL from env or client override
|
|
|
|
|
const baseURL =
|
|
|
|
|
overrides?.baseUrl || process.env.GOOGLE_VERTEX_BASE_URL
|
|
|
|
|
|
2026-01-11 00:11:30 -05:00
|
|
|
const vertexProvider = createVertex({
|
2026-01-14 03:03:30 -05:00
|
|
|
apiKey: vertexApiKey,
|
|
|
|
|
...(baseURL && { baseURL }),
|
2026-01-11 00:11:30 -05:00
|
|
|
})
|
|
|
|
|
model = vertexProvider(modelId)
|
|
|
|
|
break
|
|
|
|
|
}
|
2025-11-15 13:36:42 +09:00
|
|
|
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
case "azure": {
|
2026-01-15 21:28:22 +05:30
|
|
|
const apiKey = resolveApiKey(overrides, "AZURE_API_KEY")
|
|
|
|
|
const serverBaseUrl = resolveBaseUrlEnv(overrides, "AZURE_BASE_URL")
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
2026-01-15 21:28:22 +05:30
|
|
|
serverBaseUrl,
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
)
|
fix: prevent user API keys from using server's baseURL
When users provide their own API key but not a custom baseURL,
the code was incorrectly falling back to the server's environment
variable for baseURL. This caused user API keys to be sent to
the server's custom proxy endpoint instead of the provider's
official endpoint, resulting in 'API key format incorrect' errors.
This fix ensures that when a user provides their own API key:
- Only the user's baseUrl is used (if provided)
- Otherwise, the provider's official/default endpoint is used
- Server's baseURL env vars are never mixed with user credentials
Affected providers: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
Also fixes Azure's resourceName to not leak server config to user keys.
Fixes #577
2026-01-13 22:03:41 +09:00
|
|
|
// Only use server's resourceName if user is NOT providing their own API key
|
|
|
|
|
const resourceName = overrides?.apiKey
|
|
|
|
|
? undefined
|
|
|
|
|
: process.env.AZURE_RESOURCE_NAME
|
2025-12-11 13:32:33 +09:00
|
|
|
// Azure requires either baseURL or resourceName to construct the endpoint
|
|
|
|
|
// resourceName constructs: https://{resourceName}.openai.azure.com/openai/v1{path}
|
|
|
|
|
if (baseURL || resourceName || overrides?.apiKey) {
|
2025-12-06 12:46:40 +09:00
|
|
|
const customAzure = createAzure({
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
apiKey,
|
2025-12-11 13:32:33 +09:00
|
|
|
// baseURL takes precedence over resourceName per SDK behavior
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
...(baseURL && { baseURL }),
|
2025-12-11 13:32:33 +09:00
|
|
|
...(!baseURL && resourceName && { resourceName }),
|
2025-12-06 12:46:40 +09:00
|
|
|
})
|
|
|
|
|
model = customAzure(modelId)
|
|
|
|
|
} else {
|
|
|
|
|
model = azure(modelId)
|
|
|
|
|
}
|
|
|
|
|
break
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
}
|
2025-11-15 13:36:42 +09:00
|
|
|
|
2026-01-29 21:25:20 +09:00
|
|
|
case "ollama": {
|
|
|
|
|
const baseURL = overrides?.baseUrl || process.env.OLLAMA_BASE_URL
|
2026-02-26 13:55:21 +01:00
|
|
|
// SECURITY: When client provides a custom base URL, only use
|
|
|
|
|
// client-provided API key. Never fall back to server OLLAMA_API_KEY
|
|
|
|
|
// to prevent leaking server credentials to user-controlled endpoints.
|
|
|
|
|
const apiKey = overrides?.baseUrl
|
|
|
|
|
? overrides?.apiKey || undefined
|
|
|
|
|
: resolveApiKey(overrides, "OLLAMA_API_KEY")
|
|
|
|
|
if (baseURL || apiKey) {
|
|
|
|
|
const customOllama = createOllama({
|
|
|
|
|
...(baseURL && { baseURL }),
|
|
|
|
|
...(apiKey && {
|
|
|
|
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
|
|
|
}),
|
|
|
|
|
})
|
2025-12-06 12:46:40 +09:00
|
|
|
model = customOllama(modelId)
|
|
|
|
|
} else {
|
|
|
|
|
model = ollama(modelId)
|
|
|
|
|
}
|
|
|
|
|
break
|
2026-01-29 21:25:20 +09:00
|
|
|
}
|
2025-11-15 13:36:42 +09:00
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
case "openrouter": {
|
2026-01-15 21:28:22 +05:30
|
|
|
const apiKey = resolveApiKey(overrides, "OPENROUTER_API_KEY")
|
|
|
|
|
const serverBaseUrl = resolveBaseUrlEnv(
|
|
|
|
|
overrides,
|
|
|
|
|
"OPENROUTER_BASE_URL",
|
|
|
|
|
)
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
2026-01-15 21:28:22 +05:30
|
|
|
serverBaseUrl,
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
)
|
2025-12-06 12:46:40 +09:00
|
|
|
const openrouter = createOpenRouter({
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
apiKey,
|
|
|
|
|
...(baseURL && { baseURL }),
|
2025-12-06 12:46:40 +09:00
|
|
|
})
|
|
|
|
|
model = openrouter(modelId)
|
|
|
|
|
break
|
|
|
|
|
}
|
2025-11-15 14:29:18 +09:00
|
|
|
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
case "deepseek": {
|
2026-01-15 21:28:22 +05:30
|
|
|
const apiKey = resolveApiKey(overrides, "DEEPSEEK_API_KEY")
|
|
|
|
|
const serverBaseUrl = resolveBaseUrlEnv(
|
|
|
|
|
overrides,
|
|
|
|
|
"DEEPSEEK_BASE_URL",
|
|
|
|
|
)
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
2026-01-15 21:28:22 +05:30
|
|
|
serverBaseUrl,
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
)
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
if (baseURL || overrides?.apiKey) {
|
2025-12-06 12:46:40 +09:00
|
|
|
const customDeepSeek = createDeepSeek({
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
apiKey,
|
|
|
|
|
...(baseURL && { baseURL }),
|
2025-12-06 12:46:40 +09:00
|
|
|
})
|
|
|
|
|
model = customDeepSeek(modelId)
|
|
|
|
|
} else {
|
|
|
|
|
model = deepseek(modelId)
|
|
|
|
|
}
|
|
|
|
|
break
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
}
|
2025-12-02 11:52:09 +09:00
|
|
|
|
2025-12-07 09:22:57 +08:00
|
|
|
case "siliconflow": {
|
2026-01-15 21:28:22 +05:30
|
|
|
const apiKey = resolveApiKey(overrides, "SILICONFLOW_API_KEY")
|
|
|
|
|
const serverBaseUrl = resolveBaseUrlEnv(
|
|
|
|
|
overrides,
|
|
|
|
|
"SILICONFLOW_BASE_URL",
|
|
|
|
|
)
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
2026-01-15 21:28:22 +05:30
|
|
|
serverBaseUrl,
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
"https://api.siliconflow.cn/v1",
|
|
|
|
|
)
|
2025-12-07 09:22:57 +08:00
|
|
|
const siliconflowProvider = createOpenAI({
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
apiKey,
|
|
|
|
|
baseURL,
|
2025-12-07 09:22:57 +08:00
|
|
|
})
|
|
|
|
|
model = siliconflowProvider.chat(modelId)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-22 22:13:45 +08:00
|
|
|
case "sglang": {
|
2026-01-15 21:28:22 +05:30
|
|
|
const apiKey = resolveApiKey(overrides, "SGLANG_API_KEY")
|
|
|
|
|
const serverBaseUrl = resolveBaseUrlEnv(
|
|
|
|
|
overrides,
|
|
|
|
|
"SGLANG_BASE_URL",
|
|
|
|
|
)
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
2026-01-15 21:28:22 +05:30
|
|
|
serverBaseUrl,
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
)
|
2025-12-22 22:13:45 +08:00
|
|
|
|
|
|
|
|
const sglangProvider = createOpenAI({
|
|
|
|
|
apiKey,
|
fix: prevent user API keys from using server's baseURL
When users provide their own API key but not a custom baseURL,
the code was incorrectly falling back to the server's environment
variable for baseURL. This caused user API keys to be sent to
the server's custom proxy endpoint instead of the provider's
official endpoint, resulting in 'API key format incorrect' errors.
This fix ensures that when a user provides their own API key:
- Only the user's baseUrl is used (if provided)
- Otherwise, the provider's official/default endpoint is used
- Server's baseURL env vars are never mixed with user credentials
Affected providers: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
Also fixes Azure's resourceName to not leak server config to user keys.
Fixes #577
2026-01-13 22:03:41 +09:00
|
|
|
...(baseURL && { baseURL }),
|
2025-12-22 22:13:45 +08:00
|
|
|
// Add a custom fetch wrapper to intercept and fix the stream from sglang
|
|
|
|
|
fetch: async (url, options) => {
|
|
|
|
|
const response = await fetch(url, options)
|
|
|
|
|
if (!response.body) {
|
|
|
|
|
return response
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a transform stream to fix the non-compliant sglang stream
|
|
|
|
|
let buffer = ""
|
|
|
|
|
const decoder = new TextDecoder()
|
|
|
|
|
|
|
|
|
|
const transformStream = new TransformStream({
|
|
|
|
|
transform(chunk, controller) {
|
|
|
|
|
buffer += decoder.decode(chunk, { stream: true })
|
|
|
|
|
// Process all complete messages in the buffer
|
|
|
|
|
let messageEndPos
|
|
|
|
|
while (
|
|
|
|
|
(messageEndPos = buffer.indexOf("\n\n")) !== -1
|
|
|
|
|
) {
|
|
|
|
|
const message = buffer.substring(
|
|
|
|
|
0,
|
|
|
|
|
messageEndPos,
|
|
|
|
|
)
|
|
|
|
|
buffer = buffer.substring(messageEndPos + 2) // Move past the '\n\n'
|
|
|
|
|
|
|
|
|
|
if (message.startsWith("data: ")) {
|
|
|
|
|
const jsonStr = message.substring(6).trim()
|
|
|
|
|
if (jsonStr === "[DONE]") {
|
|
|
|
|
controller.enqueue(
|
|
|
|
|
new TextEncoder().encode(
|
|
|
|
|
message + "\n\n",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const data = JSON.parse(jsonStr)
|
|
|
|
|
const delta = data.choices?.[0]?.delta
|
|
|
|
|
|
|
|
|
|
if (delta) {
|
|
|
|
|
// Fix 1: remove invalid empty role
|
|
|
|
|
if (delta.role === "") {
|
|
|
|
|
delete delta.role
|
|
|
|
|
}
|
|
|
|
|
// Fix 2: remove non-standard reasoning_content field
|
|
|
|
|
if ("reasoning_content" in delta) {
|
|
|
|
|
delete delta.reasoning_content
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Re-serialize and forward the corrected data with the correct SSE format
|
|
|
|
|
controller.enqueue(
|
|
|
|
|
new TextEncoder().encode(
|
|
|
|
|
`data: ${JSON.stringify(data)}\n\n`,
|
|
|
|
|
),
|
|
|
|
|
)
|
2026-01-01 14:45:46 +09:00
|
|
|
} catch (_e) {
|
2025-12-22 22:13:45 +08:00
|
|
|
// If parsing fails, forward the original message to avoid breaking the stream.
|
|
|
|
|
controller.enqueue(
|
|
|
|
|
new TextEncoder().encode(
|
|
|
|
|
message + "\n\n",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} else if (message.trim() !== "") {
|
|
|
|
|
// Pass through other message types (e.g., 'event: ...')
|
|
|
|
|
controller.enqueue(
|
|
|
|
|
new TextEncoder().encode(
|
|
|
|
|
message + "\n\n",
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
flush(controller) {
|
|
|
|
|
// If there's anything left in the buffer, forward it.
|
|
|
|
|
if (buffer.trim()) {
|
|
|
|
|
controller.enqueue(
|
|
|
|
|
new TextEncoder().encode(buffer),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const transformedBody =
|
|
|
|
|
response.body.pipeThrough(transformStream)
|
|
|
|
|
|
|
|
|
|
// Return a new response with the transformed body
|
|
|
|
|
return new Response(transformedBody, {
|
|
|
|
|
status: response.status,
|
|
|
|
|
statusText: response.statusText,
|
|
|
|
|
headers: response.headers,
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
model = sglangProvider.chat(modelId)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 22:43:33 -05:00
|
|
|
case "gateway": {
|
|
|
|
|
// Vercel AI Gateway - unified access to multiple AI providers
|
|
|
|
|
// Model format: "provider/model" e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5"
|
|
|
|
|
// See: https://vercel.com/ai-gateway
|
2026-01-15 21:28:22 +05:30
|
|
|
const apiKey = resolveApiKey(overrides, "AI_GATEWAY_API_KEY")
|
|
|
|
|
const serverBaseUrl = resolveBaseUrlEnv(
|
|
|
|
|
overrides,
|
|
|
|
|
"AI_GATEWAY_BASE_URL",
|
|
|
|
|
)
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
2026-01-15 21:28:22 +05:30
|
|
|
serverBaseUrl,
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
)
|
2025-12-18 21:00:21 +08:00
|
|
|
// Only use custom configuration if explicitly set (local dev or custom Gateway)
|
|
|
|
|
// Otherwise undefined → AI SDK uses Vercel default (https://ai-gateway.vercel.sh/v1/ai) + OIDC
|
|
|
|
|
if (baseURL || overrides?.apiKey) {
|
|
|
|
|
const customGateway = createGateway({
|
|
|
|
|
apiKey,
|
|
|
|
|
...(baseURL && { baseURL }),
|
|
|
|
|
})
|
|
|
|
|
model = customGateway(modelId)
|
|
|
|
|
} else {
|
|
|
|
|
model = gateway(modelId)
|
|
|
|
|
}
|
2025-12-16 22:43:33 -05:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-30 21:13:22 +08:00
|
|
|
case "edgeone": {
|
|
|
|
|
// EdgeOne Pages Edge AI - uses OpenAI-compatible API
|
|
|
|
|
// AI SDK appends /chat/completions to baseURL
|
|
|
|
|
// /api/edgeai + /chat/completions = /api/edgeai/chat/completions
|
|
|
|
|
const baseURL = overrides?.baseUrl || "/api/edgeai"
|
|
|
|
|
const edgeoneProvider = createOpenAI({
|
|
|
|
|
apiKey: "edgeone", // Dummy key - EdgeOne doesn't require API key
|
|
|
|
|
baseURL,
|
|
|
|
|
// Pass cookies for EdgeOne Pages authentication (eo_token, eo_time)
|
|
|
|
|
...(overrides?.headers && { headers: overrides.headers }),
|
|
|
|
|
})
|
|
|
|
|
model = edgeoneProvider.chat(modelId)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 11:30:58 +09:00
|
|
|
case "doubao": {
|
2026-01-15 21:28:22 +05:30
|
|
|
const apiKey = resolveApiKey(overrides, "DOUBAO_API_KEY")
|
|
|
|
|
const serverBaseUrl = resolveBaseUrlEnv(
|
|
|
|
|
overrides,
|
|
|
|
|
"DOUBAO_BASE_URL",
|
|
|
|
|
)
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
2026-01-15 21:28:22 +05:30
|
|
|
serverBaseUrl,
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
"https://ark.cn-beijing.volces.com/api/v3",
|
|
|
|
|
)
|
2026-01-05 23:09:09 +09:00
|
|
|
const lowerModelId = modelId.toLowerCase()
|
|
|
|
|
// Use DeepSeek provider for DeepSeek/Kimi models, OpenAI for others (multimodal support)
|
|
|
|
|
if (
|
|
|
|
|
lowerModelId.includes("deepseek") ||
|
|
|
|
|
lowerModelId.includes("kimi")
|
|
|
|
|
) {
|
|
|
|
|
const doubaoProvider = createDeepSeek({
|
|
|
|
|
apiKey,
|
|
|
|
|
baseURL,
|
|
|
|
|
})
|
|
|
|
|
model = doubaoProvider(modelId)
|
|
|
|
|
} else {
|
|
|
|
|
const doubaoProvider = createOpenAI({
|
|
|
|
|
apiKey,
|
|
|
|
|
baseURL,
|
|
|
|
|
})
|
|
|
|
|
model = doubaoProvider.chat(modelId)
|
|
|
|
|
}
|
2025-12-29 11:30:58 +09:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-06 18:41:25 +08:00
|
|
|
case "modelscope": {
|
2026-01-15 21:28:22 +05:30
|
|
|
const apiKey = resolveApiKey(overrides, "MODELSCOPE_API_KEY")
|
|
|
|
|
const serverBaseUrl = resolveBaseUrlEnv(
|
|
|
|
|
overrides,
|
|
|
|
|
"MODELSCOPE_BASE_URL",
|
|
|
|
|
)
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
2026-01-15 21:28:22 +05:30
|
|
|
serverBaseUrl,
|
refactor: use resolveBaseURL utility in all providers
Refactored all 11 providers to use the resolveBaseURL() utility function
instead of inline ternary expressions. This ensures:
1. The security fix is centralized in one testable function
2. Unit tests actually validate the production code path
3. Future changes only need to modify one location
Providers refactored: openai, anthropic, google, azure, openrouter,
deepseek, siliconflow, sglang, gateway, doubao, modelscope
2026-01-13 22:21:22 +09:00
|
|
|
"https://api-inference.modelscope.cn/v1",
|
|
|
|
|
)
|
2026-01-06 18:41:25 +08:00
|
|
|
const modelscopeProvider = createOpenAI({
|
|
|
|
|
apiKey,
|
|
|
|
|
baseURL,
|
|
|
|
|
})
|
|
|
|
|
model = modelscopeProvider.chat(modelId)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
case "minimax": {
|
|
|
|
|
const apiKey = resolveApiKey(overrides, "MINIMAX_API_KEY")
|
|
|
|
|
const serverBaseUrl = resolveBaseUrlEnv(
|
|
|
|
|
overrides,
|
|
|
|
|
"MINIMAX_BASE_URL",
|
|
|
|
|
)
|
|
|
|
|
const rawBaseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
|
|
|
|
serverBaseUrl,
|
|
|
|
|
PROVIDER_INFO.minimax.defaultBaseUrl,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (!rawBaseURL) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
"MiniMax base URL could not be resolved. Set MINIMAX_BASE_URL or configure a base URL in settings.",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { baseURL, isAnthropicCompatible } =
|
|
|
|
|
normalizeMiniMaxBaseURL(rawBaseURL)
|
|
|
|
|
|
|
|
|
|
if (isAnthropicCompatible) {
|
|
|
|
|
const minimax = createAnthropic({ apiKey, baseURL })
|
|
|
|
|
model = minimax.chat(modelId)
|
|
|
|
|
} else {
|
|
|
|
|
const minimax = createOpenAI({ apiKey, baseURL })
|
|
|
|
|
model = minimax.chat(modelId)
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case "glm":
|
|
|
|
|
case "qwen":
|
|
|
|
|
case "qiniu":
|
2026-03-24 19:48:34 +08:00
|
|
|
case "novita": {
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
const envVar = PROVIDER_ENV_VARS[provider]
|
|
|
|
|
if (!envVar) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`API key environment variable not defined for provider: ${provider}`,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
const apiKey = resolveApiKey(overrides, envVar)
|
|
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
|
|
|
|
resolveBaseUrlEnv(
|
|
|
|
|
overrides,
|
|
|
|
|
`${provider.toUpperCase()}_BASE_URL`,
|
|
|
|
|
),
|
|
|
|
|
PROVIDER_INFO[provider]?.defaultBaseUrl,
|
|
|
|
|
)
|
|
|
|
|
const customProvider = createOpenAI({
|
|
|
|
|
apiKey,
|
|
|
|
|
baseURL,
|
|
|
|
|
})
|
|
|
|
|
model = customProvider.chat(modelId)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 13:02:26 +08:00
|
|
|
case "kimi": {
|
|
|
|
|
const apiKey = resolveApiKey(overrides, "KIMI_API_KEY")
|
|
|
|
|
const baseURL = resolveBaseURL(
|
|
|
|
|
overrides?.apiKey,
|
|
|
|
|
overrides?.baseUrl,
|
|
|
|
|
resolveBaseUrlEnv(overrides, "KIMI_BASE_URL"),
|
|
|
|
|
PROVIDER_INFO["kimi"]?.defaultBaseUrl,
|
|
|
|
|
)
|
|
|
|
|
// Use createDeepSeek to properly handle reasoning_content for Kimi
|
|
|
|
|
// thinking models (e.g., kimi-k2.6). Kimi's API uses the same
|
|
|
|
|
// reasoning_content field as DeepSeek, so this provider correctly
|
|
|
|
|
// captures and replays reasoning in multi-turn conversations.
|
|
|
|
|
const customProvider = createDeepSeek({ apiKey, baseURL })
|
|
|
|
|
model = customProvider(modelId)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
default:
|
|
|
|
|
throw new Error(
|
2026-03-24 19:48:34 +08:00
|
|
|
`Unknown AI provider: ${provider}. Supported providers: bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow, sglang, gateway, edgeone, doubao, modelscope, glm, qwen, qiniu, kimi, minimax, novita`,
|
2025-12-06 12:46:40 +09:00
|
|
|
)
|
|
|
|
|
}
|
2025-11-15 13:36:42 +09:00
|
|
|
|
2025-12-10 20:54:43 +05:30
|
|
|
// Apply provider-specific options for all providers except bedrock (which has special handling)
|
|
|
|
|
if (customProviderOptions && provider !== "bedrock" && !providerOptions) {
|
|
|
|
|
providerOptions = customProviderOptions
|
|
|
|
|
}
|
|
|
|
|
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
return { model, providerOptions, headers, modelId, provider }
|
2025-11-15 13:36:42 +09:00
|
|
|
}
|
2025-12-09 15:53:59 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if a model supports prompt caching.
|
|
|
|
|
* Currently only Claude models on Bedrock support prompt caching.
|
|
|
|
|
*/
|
|
|
|
|
export function supportsPromptCaching(modelId: string): boolean {
|
|
|
|
|
// Bedrock prompt caching is supported for Claude models
|
|
|
|
|
return (
|
|
|
|
|
modelId.includes("claude") ||
|
|
|
|
|
modelId.includes("anthropic") ||
|
|
|
|
|
modelId.startsWith("us.anthropic") ||
|
|
|
|
|
modelId.startsWith("eu.anthropic")
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-12-31 12:20:09 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if a model supports image/vision input.
|
|
|
|
|
* Some models silently drop image parts without error (AI SDK warning only).
|
|
|
|
|
*/
|
|
|
|
|
export function supportsImageInput(modelId: string): boolean {
|
|
|
|
|
const lowerModelId = modelId.toLowerCase()
|
|
|
|
|
|
|
|
|
|
// Helper to check if model has vision capability indicator
|
|
|
|
|
const hasVisionIndicator =
|
|
|
|
|
lowerModelId.includes("vision") || lowerModelId.includes("vl")
|
|
|
|
|
|
|
|
|
|
// Models that DON'T support image/vision input (unless vision variant)
|
2026-02-02 00:14:27 +09:00
|
|
|
// Kimi K2 doesn't support images, but K2.5 does
|
|
|
|
|
// Only block kimi-k2 specifically, not other Kimi models
|
|
|
|
|
if (
|
|
|
|
|
(lowerModelId.includes("kimi-k2") ||
|
|
|
|
|
lowerModelId.includes("kimi_k2")) &&
|
|
|
|
|
!hasVisionIndicator &&
|
|
|
|
|
!lowerModelId.includes("2.5") &&
|
|
|
|
|
!lowerModelId.includes("k2.5")
|
|
|
|
|
) {
|
2025-12-31 12:20:09 +09:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 00:22:44 +08:00
|
|
|
// Moonshot text models (moonshot-v1 series are text-only)
|
|
|
|
|
if (lowerModelId.includes("moonshot-v1") && !hasVisionIndicator) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 18:38:49 +08:00
|
|
|
// MiniMax text models (MiniMax-M2.x series are text-only; M3 supports image input)
|
|
|
|
|
if (
|
|
|
|
|
lowerModelId.includes("minimax") &&
|
|
|
|
|
!hasVisionIndicator &&
|
|
|
|
|
!lowerModelId.includes("m3")
|
|
|
|
|
) {
|
2026-03-21 00:22:44 +08:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-31 12:20:09 +09:00
|
|
|
// DeepSeek text models (not vision variants)
|
|
|
|
|
if (lowerModelId.includes("deepseek") && !hasVisionIndicator) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Qwen text models (not vision variants like qwen-vl)
|
2026-04-10 09:23:39 +08:00
|
|
|
// Qwen3.5 series (qwen3.5, qwen3.5-plus, qwen3.5-flash) natively support image input
|
2026-04-13 19:05:57 +08:00
|
|
|
// QvQ (Qwen Visual QA) models are vision models — exclude them even when prefixed with "qwen/"
|
2026-02-26 19:45:57 +08:00
|
|
|
if (
|
|
|
|
|
lowerModelId.includes("qwen") &&
|
|
|
|
|
!hasVisionIndicator &&
|
2026-04-13 19:05:57 +08:00
|
|
|
!lowerModelId.includes("qwen3.5") &&
|
|
|
|
|
!lowerModelId.includes("qvq")
|
2026-02-26 19:45:57 +08:00
|
|
|
) {
|
2025-12-31 12:20:09 +09:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 00:22:44 +08:00
|
|
|
// GLM text models (not vision variants)
|
|
|
|
|
// GLM vision models: glm-4v, glm-4v-9b, glm-4.1v-9b-thinking
|
|
|
|
|
if (lowerModelId.includes("glm") && !hasVisionIndicator) {
|
|
|
|
|
if (!/[\d.]v/.test(lowerModelId)) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-31 12:20:09 +09:00
|
|
|
// Default: assume model supports images
|
|
|
|
|
return true
|
|
|
|
|
}
|
2026-01-20 20:52:04 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the AI model for diagram validation.
|
|
|
|
|
* Uses VALIDATION_MODEL env var if set, otherwise falls back to AI_MODEL.
|
|
|
|
|
* Throws if the model doesn't support image input.
|
|
|
|
|
*/
|
|
|
|
|
export function getValidationModel(): ReturnType<typeof getAIModel>["model"] {
|
|
|
|
|
const modelId = process.env.VALIDATION_MODEL || process.env.AI_MODEL
|
|
|
|
|
|
|
|
|
|
if (!modelId) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
"No validation model configured. Set VALIDATION_MODEL or AI_MODEL.",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!supportsImageInput(modelId)) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Validation requires a vision-capable model. Model "${modelId}" does not support image input.`,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { model } = getAIModel({ modelId })
|
|
|
|
|
return model
|
|
|
|
|
}
|