diff --git a/docs/providers/minimax.md b/docs/providers/minimax.md index 21d85686b..e057043a9 100644 --- a/docs/providers/minimax.md +++ b/docs/providers/minimax.md @@ -145,13 +145,50 @@ Use the interactive config wizard to set MiniMax without editing JSON: ## Configuration options -- `models.providers.minimax.baseUrl`: prefer `https://api.minimax.io/anthropic` (Anthropic-compatible); `https://api.minimax.io/v1` is optional for OpenAI-compatible payloads. +- `models.providers.minimax.baseUrl`: auto-detected based on timezone: + - China mainland/HK/TW: `https://api.minimaxi.com/anthropic` (domestic) + - Overseas: `https://api.minimax.io/anthropic` (default) + - For OpenAI-compatible payloads, replace `/anthropic` with `/v1` - `models.providers.minimax.api`: prefer `anthropic-messages`; `openai-completions` is optional for OpenAI-compatible payloads. - `models.providers.minimax.apiKey`: MiniMax API key (`MINIMAX_API_KEY`). - `models.providers.minimax.models`: define `id`, `name`, `reasoning`, `contextWindow`, `maxTokens`, `cost`. - `agents.defaults.models`: alias models you want in the allowlist. - `models.mode`: keep `merge` if you want to add MiniMax alongside built-ins. +## China Mainland Users + +OpenClaw automatically detects China mainland timezones (Asia/Shanghai, Asia/Hong_Kong, +Asia/Taipei, etc.) and uses the domestic endpoint (`api.minimaxi.com`) for faster, +more reliable connections. + +> [!NOTE] +> The domain difference is subtle: `minimaxi.com` (with extra 'i') vs `minimax.io`. + +To manually override the baseUrl (e.g., if timezone detection doesn't work for your setup): + +```bash +openclaw config set models.providers.minimax.baseUrl "https://api.minimaxi.com/anthropic" +``` + +Or edit `openclaw.json` directly: + +```json5 +{ + models: { + providers: { + minimax: { + baseUrl: "https://api.minimaxi.com/anthropic", + apiKey: "${MINIMAX_API_KEY}", + api: "anthropic-messages", + models: [...] + } + } + } +} +``` + +Then restart the gateway with `openclaw gateway restart`. + ## Notes - Model refs are `minimax/`. diff --git a/src/agents/minimax-vlm.ts b/src/agents/minimax-vlm.ts index 4244877ad..551247690 100644 --- a/src/agents/minimax-vlm.ts +++ b/src/agents/minimax-vlm.ts @@ -1,3 +1,5 @@ +import { isChinaRegion } from "../infra/region-utils.js"; + type MinimaxBaseResp = { status_code?: number; status_msg?: string; @@ -9,11 +11,12 @@ function coerceApiHost(params: { env?: NodeJS.ProcessEnv; }): string { const env = params.env ?? process.env; + const defaultHost = isChinaRegion() ? "https://api.minimaxi.com" : "https://api.minimax.io"; const raw = params.apiHost?.trim() || env.MINIMAX_API_HOST?.trim() || params.modelBaseUrl?.trim() || - "https://api.minimax.io"; + defaultHost; try { const url = new URL(raw); @@ -24,7 +27,7 @@ function coerceApiHost(params: { const url = new URL(`https://${raw}`); return url.origin; } catch { - return "https://api.minimax.io"; + return defaultHost; } } diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 0cd034c82..7be9b2174 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -13,11 +13,14 @@ import { SYNTHETIC_MODEL_CATALOG, } from "./synthetic-models.js"; import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js"; +import { isChinaRegion } from "../infra/region-utils.js"; type ModelsConfig = NonNullable; export type ProviderConfig = NonNullable[string]; -const MINIMAX_API_BASE_URL = "https://api.minimax.chat/v1"; +function getMinimaxImplicitBaseUrl(): string { + return isChinaRegion() ? "https://api.minimaxi.com/v1" : "https://api.minimax.chat/v1"; +} const MINIMAX_DEFAULT_MODEL_ID = "MiniMax-M2.1"; const MINIMAX_DEFAULT_VISION_MODEL_ID = "MiniMax-VL-01"; const MINIMAX_DEFAULT_CONTEXT_WINDOW = 200000; @@ -254,7 +257,7 @@ export function normalizeProviders(params: { function buildMinimaxProvider(): ProviderConfig { return { - baseUrl: MINIMAX_API_BASE_URL, + baseUrl: getMinimaxImplicitBaseUrl(), api: "openai-completions", models: [ { diff --git a/src/commands/onboard-auth.config-minimax.ts b/src/commands/onboard-auth.config-minimax.ts index dd619d6fd..965541f37 100644 --- a/src/commands/onboard-auth.config-minimax.ts +++ b/src/commands/onboard-auth.config-minimax.ts @@ -1,11 +1,11 @@ import type { OpenClawConfig } from "../config/config.js"; +import { isChinaRegion } from "../infra/region-utils.js"; import { buildMinimaxApiModelDefinition, buildMinimaxModelDefinition, - DEFAULT_MINIMAX_BASE_URL, DEFAULT_MINIMAX_CONTEXT_WINDOW, DEFAULT_MINIMAX_MAX_TOKENS, - MINIMAX_API_BASE_URL, + getMinimaxBaseUrl, MINIMAX_HOSTED_COST, MINIMAX_HOSTED_MODEL_ID, MINIMAX_HOSTED_MODEL_REF, @@ -81,7 +81,7 @@ export function applyMinimaxHostedProviderConfig( const mergedModels = hasHostedModel ? existingModels : [...existingModels, hostedModel]; providers.minimax = { ...existingProvider, - baseUrl: params?.baseUrl?.trim() || DEFAULT_MINIMAX_BASE_URL, + baseUrl: params?.baseUrl?.trim() || getMinimaxBaseUrl(isChinaRegion(), "v1"), apiKey: "minimax", api: "openai-completions", models: mergedModels.length > 0 ? mergedModels : [hostedModel], @@ -164,7 +164,7 @@ export function applyMinimaxApiProviderConfig( const normalizedApiKey = resolvedApiKey?.trim() === "minimax" ? "" : resolvedApiKey; providers.minimax = { ...existingProviderRest, - baseUrl: MINIMAX_API_BASE_URL, + baseUrl: getMinimaxBaseUrl(isChinaRegion(), "anthropic"), api: "anthropic-messages", ...(normalizedApiKey?.trim() ? { apiKey: normalizedApiKey } : {}), models: mergedModels.length > 0 ? mergedModels : [apiModel], diff --git a/src/commands/onboard-auth.models.ts b/src/commands/onboard-auth.models.ts index de5a4edaa..5ba6978e4 100644 --- a/src/commands/onboard-auth.models.ts +++ b/src/commands/onboard-auth.models.ts @@ -2,6 +2,26 @@ import type { ModelDefinitionConfig } from "../config/types.js"; export const DEFAULT_MINIMAX_BASE_URL = "https://api.minimax.io/v1"; export const MINIMAX_API_BASE_URL = "https://api.minimax.io/anthropic"; + +export const MINIMAX_CHINA_BASE_URL = "https://api.minimaxi.com/v1"; +export const MINIMAX_CHINA_API_BASE_URL = "https://api.minimaxi.com/anthropic"; + +/** + * Returns the appropriate MiniMax base URL based on region. + * + * @param isChinaRegion - Whether the user is in China/Greater China + * @param api - Which API variant to use: "v1" for OpenAI-compatible, "anthropic" for Anthropic-compatible + * @returns The appropriate base URL for the region and API variant + */ +export function getMinimaxBaseUrl( + isChinaRegion: boolean, + api: "v1" | "anthropic" = "anthropic", +): string { + if (isChinaRegion) { + return api === "v1" ? MINIMAX_CHINA_BASE_URL : MINIMAX_CHINA_API_BASE_URL; + } + return api === "v1" ? DEFAULT_MINIMAX_BASE_URL : MINIMAX_API_BASE_URL; +} export const MINIMAX_HOSTED_MODEL_ID = "MiniMax-M2.1"; export const MINIMAX_HOSTED_MODEL_REF = `minimax/${MINIMAX_HOSTED_MODEL_ID}`; export const DEFAULT_MINIMAX_CONTEXT_WINDOW = 200000; diff --git a/src/infra/region-utils.ts b/src/infra/region-utils.ts new file mode 100644 index 000000000..0e7fca55e --- /dev/null +++ b/src/infra/region-utils.ts @@ -0,0 +1,52 @@ +/** + * Region detection utilities for provider endpoint selection. + * + * Some providers (e.g., MiniMax) have region-specific endpoints. This module + * provides timezone-based region detection to automatically select the + * appropriate endpoint for users in different regions. + */ + +/** + * Timezone identifiers for China and Greater China regions. + * These timezones indicate the user is likely in mainland China, Hong Kong, + * Macau, or Taiwan and may benefit from using domestic API endpoints. + */ +const CHINA_TIMEZONES = new Set([ + "Asia/Shanghai", + "Asia/Chongqing", + "Asia/Harbin", + "Asia/Urumqi", + "PRC", + "Asia/Hong_Kong", + "Hongkong", + "Asia/Macau", + "Asia/Taipei", + "ROC", +]); + +/** + * Detects if the current environment is likely in China or Greater China + * based on the system timezone. + * + * @returns `true` if the timezone indicates China/Greater China region + * + * @example + * ```ts + * if (isChinaRegion()) { + * // Use domestic API endpoint + * baseUrl = "https://api.minimaxi.com/anthropic"; + * } else { + * // Use overseas API endpoint + * baseUrl = "https://api.minimax.io/anthropic"; + * } + * ``` + */ +export function isChinaRegion(): boolean { + try { + const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; + return CHINA_TIMEZONES.has(tz); + } catch { + // If timezone detection fails, default to overseas + return false; + } +}