diff --git a/packages/cli/src/constants/codingPlan.ts b/packages/cli/src/constants/codingPlan.ts
index 845f85d19..0a3084658 100644
--- a/packages/cli/src/constants/codingPlan.ts
+++ b/packages/cli/src/constants/codingPlan.ts
@@ -22,78 +22,11 @@ export enum CodingPlanRegion {
export type CodingPlanTemplate = ModelConfig[];
/**
- * Environment variable key for storing the coding plan API key (China/Bailian)
+ * Environment variable key for storing the coding plan API key.
+ * Unified key for both regions since they are mutually exclusive.
*/
export const CODING_PLAN_ENV_KEY = 'BAILIAN_CODING_PLAN_API_KEY';
-/**
- * Environment variable key for storing the coding plan API key (Global/Intl)
- */
-export const CODING_PLAN_INTL_ENV_KEY = 'BAILIAN_CODING_PLAN_INTL_API_KEY';
-
-/**
- * Base URL for China/Bailian Coding Plan
- */
-export const CODING_PLAN_BASE_URL = 'https://coding.dashscope.aliyuncs.com/v1';
-
-/**
- * Base URL for Global/Intl Coding Plan
- */
-export const CODING_PLAN_INTL_BASE_URL =
- 'https://coding-intl.dashscope.aliyuncs.com/v1';
-
-/**
- * CODING_PLAN_MODELS defines the model configurations for coding-plan mode (China/Bailian).
- */
-export const CODING_PLAN_MODELS: CodingPlanTemplate = [
- {
- id: 'qwen3-coder-plus',
- name: 'qwen3-coder-plus',
- baseUrl: CODING_PLAN_BASE_URL,
- description: 'qwen3-coder-plus model from Bailian Coding Plan',
- envKey: CODING_PLAN_ENV_KEY,
- },
- {
- id: 'qwen3-max-2026-01-23',
- name: 'qwen3-max-2026-01-23',
- description:
- 'qwen3-max model with thinking enabled from Bailian Coding Plan',
- baseUrl: CODING_PLAN_BASE_URL,
- envKey: CODING_PLAN_ENV_KEY,
- generationConfig: {
- extra_body: {
- enable_thinking: true,
- },
- },
- },
-];
-
-/**
- * CODING_PLAN_INTL_MODELS defines the model configurations for coding-plan mode (Global/Intl).
- */
-export const CODING_PLAN_INTL_MODELS: CodingPlanTemplate = [
- {
- id: 'qwen3-coder-plus',
- name: 'qwen3-coder-plus',
- baseUrl: CODING_PLAN_INTL_BASE_URL,
- description: 'qwen3-coder-plus model from Coding Plan (Global/Intl)',
- envKey: CODING_PLAN_INTL_ENV_KEY,
- },
- {
- id: 'qwen3-max-2026-01-23',
- name: 'qwen3-max-2026-01-23',
- description:
- 'qwen3-max model with thinking enabled from Coding Plan (Global/Intl)',
- baseUrl: CODING_PLAN_INTL_BASE_URL,
- envKey: CODING_PLAN_INTL_ENV_KEY,
- generationConfig: {
- extra_body: {
- enable_thinking: true,
- },
- },
- },
-];
-
/**
* Computes the version hash for the coding plan template.
* Uses SHA256 of the JSON-serialized template for deterministic versioning.
@@ -106,81 +39,149 @@ export function computeCodingPlanVersion(template: CodingPlanTemplate): string {
}
/**
- * Current version of the China/Bailian coding plan template.
- * Computed at runtime from the template content.
+ * Generate the complete coding plan template for a specific region.
+ * China region uses legacy description to maintain backward compatibility.
+ * Global region uses new description with region indicator.
+ * @param region - The region to generate template for
+ * @returns Complete model configuration array for the region
*/
-export const CODING_PLAN_VERSION = computeCodingPlanVersion(CODING_PLAN_MODELS);
+export function generateCodingPlanTemplate(
+ region: CodingPlanRegion,
+): CodingPlanTemplate {
+ if (region === CodingPlanRegion.CHINA) {
+ // China region uses legacy fields to maintain backward compatibility
+ // This ensures existing users don't get prompted for unnecessary updates
+ return [
+ {
+ id: 'qwen3-coder-plus',
+ name: 'qwen3-coder-plus',
+ baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',
+ description: 'qwen3-coder-plus model from Bailian Coding Plan',
+ envKey: CODING_PLAN_ENV_KEY,
+ },
+ {
+ id: 'qwen3-max-2026-01-23',
+ name: 'qwen3-max-2026-01-23',
+ description:
+ 'qwen3-max model with thinking enabled from Bailian Coding Plan',
+ baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',
+ envKey: CODING_PLAN_ENV_KEY,
+ generationConfig: {
+ extra_body: {
+ enable_thinking: true,
+ },
+ },
+ },
+ ];
+ }
+
+ // Global region uses new description with region indicator
+ return [
+ {
+ id: 'qwen3-coder-plus',
+ name: 'qwen3-coder-plus',
+ baseUrl: 'https://coding-intl.dashscope.aliyuncs.com/v1',
+ description: 'qwen3-coder-plus model from Coding Plan (Global/Intl)',
+ envKey: CODING_PLAN_ENV_KEY,
+ },
+ {
+ id: 'qwen3-max-2026-01-23',
+ name: 'qwen3-max-2026-01-23',
+ description:
+ 'qwen3-max model with thinking enabled from Coding Plan (Global/Intl)',
+ baseUrl: 'https://coding-intl.dashscope.aliyuncs.com/v1',
+ envKey: CODING_PLAN_ENV_KEY,
+ generationConfig: {
+ extra_body: {
+ enable_thinking: true,
+ },
+ },
+ },
+ ];
+}
/**
- * Current version of the Global/Intl coding plan template.
- * Computed at runtime from the template content.
+ * Get the complete configuration for a specific region.
+ * @param region - The region to use
+ * @returns Object containing template, baseUrl, and version
*/
-export const CODING_PLAN_INTL_VERSION = computeCodingPlanVersion(
- CODING_PLAN_INTL_MODELS,
-);
+export function getCodingPlanConfig(region: CodingPlanRegion) {
+ const template = generateCodingPlanTemplate(region);
+ const baseUrl =
+ region === CodingPlanRegion.CHINA
+ ? 'https://coding.dashscope.aliyuncs.com/v1'
+ : 'https://coding-intl.dashscope.aliyuncs.com/v1';
+ const regionName =
+ region === CodingPlanRegion.CHINA
+ ? 'Bailian Coding Plan (China)'
+ : 'Bailian Coding Plan (Global/Intl)';
+
+ return {
+ template,
+ baseUrl,
+ regionName,
+ version: computeCodingPlanVersion(template),
+ };
+}
/**
- * All coding plan templates for both regions.
- * Used for update detection and filtering.
+ * Get all unique base URLs for coding plan (used for filtering/config detection).
+ * @returns Array of base URLs
*/
-export const ALL_CODING_PLAN_TEMPLATES: CodingPlanTemplate = [
- ...CODING_PLAN_MODELS,
- ...CODING_PLAN_INTL_MODELS,
-];
+export function getCodingPlanBaseUrls(): string[] {
+ return [
+ 'https://coding.dashscope.aliyuncs.com/v1',
+ 'https://coding-intl.dashscope.aliyuncs.com/v1',
+ ];
+}
/**
- * Check if a config belongs to any Coding Plan template (China or Intl).
+ * Check if a config belongs to Coding Plan (any region).
+ * Returns the region if matched, or false if not a Coding Plan config.
* @param baseUrl - The baseUrl to check
* @param envKey - The envKey to check
- * @param region - Optional region to limit the check to a specific region
- * @returns true if the config matches any Coding Plan template
+ * @returns The region if matched, false otherwise
*/
export function isCodingPlanConfig(
baseUrl: string | undefined,
envKey: string | undefined,
- region?: CodingPlanRegion,
-): boolean {
+): CodingPlanRegion | false {
if (!baseUrl || !envKey) {
return false;
}
- // If region is specified, only check that region's templates
- if (region === CodingPlanRegion.GLOBAL) {
- return CODING_PLAN_INTL_MODELS.some(
- (template) => template.baseUrl === baseUrl && template.envKey === envKey,
- );
- } else if (region === CodingPlanRegion.CHINA) {
- return CODING_PLAN_MODELS.some(
- (template) => template.baseUrl === baseUrl && template.envKey === envKey,
- );
+ // Must use the unified envKey
+ if (envKey !== CODING_PLAN_ENV_KEY) {
+ return false;
}
- // No region specified, check all templates
- return ALL_CODING_PLAN_TEMPLATES.some(
- (template) => template.baseUrl === baseUrl && template.envKey === envKey,
- );
+ // Check which region's baseUrl matches
+ if (baseUrl === 'https://coding.dashscope.aliyuncs.com/v1') {
+ return CodingPlanRegion.CHINA;
+ }
+ if (baseUrl === 'https://coding-intl.dashscope.aliyuncs.com/v1') {
+ return CodingPlanRegion.GLOBAL;
+ }
+
+ return false;
}
/**
- * Get the appropriate template and env key for the selected region.
- * @param region - The region to use (default: CHINA)
- * @returns Object containing template, envKey, version, and baseUrl
+ * Get region from baseUrl.
+ * @param baseUrl - The baseUrl to check
+ * @returns The region if matched, null otherwise
*/
-export function getCodingPlanConfig(
- region: CodingPlanRegion = CodingPlanRegion.CHINA,
-) {
- if (region === CodingPlanRegion.GLOBAL) {
- return {
- template: CODING_PLAN_INTL_MODELS,
- envKey: CODING_PLAN_INTL_ENV_KEY,
- version: CODING_PLAN_INTL_VERSION,
- baseUrl: CODING_PLAN_INTL_BASE_URL,
- };
+export function getRegionFromBaseUrl(
+ baseUrl: string | undefined,
+): CodingPlanRegion | null {
+ if (!baseUrl) return null;
+
+ if (baseUrl === 'https://coding.dashscope.aliyuncs.com/v1') {
+ return CodingPlanRegion.CHINA;
}
- return {
- template: CODING_PLAN_MODELS,
- envKey: CODING_PLAN_ENV_KEY,
- version: CODING_PLAN_VERSION,
- baseUrl: CODING_PLAN_BASE_URL,
- };
+ if (baseUrl === 'https://coding-intl.dashscope.aliyuncs.com/v1') {
+ return CodingPlanRegion.GLOBAL;
+ }
+
+ return null;
}
diff --git a/packages/cli/src/i18n/locales/de.js b/packages/cli/src/i18n/locales/de.js
index 291e14516..2a00a7b9e 100644
--- a/packages/cli/src/i18n/locales/de.js
+++ b/packages/cli/src/i18n/locales/de.js
@@ -1418,11 +1418,11 @@ export default {
// ============================================================================
'Coding Plan': 'Coding Plan',
'Coding Plan (Bailian, China)': 'Coding Plan (Bailian, China)',
- 'Coding Plan (Bailian, Global/Intl)': 'Coding Plan (Bailian, Global/Intl)',
+ 'Bailian Coding Plan (Global/Intl)': 'Bailian Coding Plan (Global/Intl)',
"Paste your api key of Bailian Coding Plan and you're all set!":
'Fügen Sie Ihren Bailian Coding Plan API-Schlüssel ein und Sie sind bereit!',
- "Paste your api key of Coding Plan (Global/Intl) and you're all set!":
- 'Fügen Sie Ihren Coding Plan (Global/Intl) API-Schlüssel ein und Sie sind bereit!',
+ "Paste your api key of Bailian Coding Plan (Global/Intl) and you're all set!":
+ 'Fügen Sie Ihren Bailian Coding Plan (Global/Intl) API-Schlüssel ein und Sie sind bereit!',
Custom: 'Benutzerdefiniert',
'More instructions about configuring `modelProviders` manually.':
'Weitere Anweisungen zur manuellen Konfiguration von `modelProviders`.',
@@ -1438,11 +1438,10 @@ export default {
// ============================================================================
'New model configurations are available for Bailian Coding Plan (China). Update now?':
'Neue Modellkonfigurationen sind für Bailian Coding Plan (China) verfügbar. Jetzt aktualisieren?',
- 'New model configurations are available for Coding Plan (Global/Intl). Update now?':
- 'Neue Modellkonfigurationen sind für Coding Plan (Global/Intl) verfügbar. Jetzt aktualisieren?',
+ 'New model configurations are available for Bailian Coding Plan (Global/Intl). Update now?':
+ 'Neue Modellkonfigurationen sind für Bailian Coding Plan (Global/Intl) verfügbar. Jetzt aktualisieren?',
'{{region}} configuration updated successfully. New models are now available.':
'{{region}}-Konfiguration erfolgreich aktualisiert. Neue Modelle sind jetzt verfügbar.',
'Authenticated successfully with {{region}}. API key is stored in settings.env.':
'Erfolgreich mit {{region}} authentifiziert. API-Schlüssel ist in settings.env gespeichert.',
- 'Coding Plan (Global/Intl)': 'Coding Plan (Global/Intl)',
};
diff --git a/packages/cli/src/i18n/locales/en.js b/packages/cli/src/i18n/locales/en.js
index a650d927f..234ff3ab8 100644
--- a/packages/cli/src/i18n/locales/en.js
+++ b/packages/cli/src/i18n/locales/en.js
@@ -1419,11 +1419,11 @@ export default {
// ============================================================================
'Coding Plan': 'Coding Plan',
'Coding Plan (Bailian, China)': 'Coding Plan (Bailian, China)',
- 'Coding Plan (Bailian, Global/Intl)': 'Coding Plan (Bailian, Global/Intl)',
+ 'Bailian Coding Plan (Global/Intl)': 'Bailian Coding Plan (Global/Intl)',
"Paste your api key of Bailian Coding Plan and you're all set!":
"Paste your api key of Bailian Coding Plan and you're all set!",
- "Paste your api key of Coding Plan (Global/Intl) and you're all set!":
- "Paste your api key of Coding Plan (Global/Intl) and you're all set!",
+ "Paste your api key of Bailian Coding Plan (Global/Intl) and you're all set!":
+ "Paste your api key of Bailian Coding Plan (Global/Intl) and you're all set!",
Custom: 'Custom',
'More instructions about configuring `modelProviders` manually.':
'More instructions about configuring `modelProviders` manually.',
@@ -1437,11 +1437,10 @@ export default {
// ============================================================================
'New model configurations are available for Bailian Coding Plan (China). Update now?':
'New model configurations are available for Bailian Coding Plan (China). Update now?',
- 'New model configurations are available for Coding Plan (Global/Intl). Update now?':
- 'New model configurations are available for Coding Plan (Global/Intl). Update now?',
+ 'New model configurations are available for Bailian Coding Plan (Global/Intl). Update now?':
+ 'New model configurations are available for Bailian Coding Plan (Global/Intl). Update now?',
'{{region}} configuration updated successfully. New models are now available.':
'{{region}} configuration updated successfully. New models are now available.',
'Authenticated successfully with {{region}}. API key is stored in settings.env.':
'Authenticated successfully with {{region}}. API key is stored in settings.env.',
- 'Coding Plan (Global/Intl)': 'Coding Plan (Global/Intl)',
};
diff --git a/packages/cli/src/i18n/locales/ja.js b/packages/cli/src/i18n/locales/ja.js
index e20e33e55..e094dfb09 100644
--- a/packages/cli/src/i18n/locales/ja.js
+++ b/packages/cli/src/i18n/locales/ja.js
@@ -929,12 +929,11 @@ export default {
// ============================================================================
'Coding Plan': 'Coding Plan',
'Coding Plan (Bailian, China)': 'Coding Plan (Bailian, 中国)',
- 'Coding Plan (Bailian, Global/Intl)':
- 'Coding Plan (Bailian, グローバル/国際)',
+ 'Bailian Coding Plan (Global/Intl)': 'Bailian Coding Plan (グローバル/国際)',
"Paste your api key of Bailian Coding Plan and you're all set!":
'Bailian Coding PlanのAPIキーを貼り付けるだけで準備完了です!',
- "Paste your api key of Coding Plan (Global/Intl) and you're all set!":
- 'Coding Plan (グローバル/国際) のAPIキーを貼り付けるだけで準備完了です!',
+ "Paste your api key of Bailian Coding Plan (Global/Intl) and you're all set!":
+ 'Bailian Coding Plan (グローバル/国際) のAPIキーを貼り付けるだけで準備完了です!',
Custom: 'カスタム',
'More instructions about configuring `modelProviders` manually.':
'`modelProviders`を手動で設定する方法の詳細はこちら。',
@@ -949,11 +948,10 @@ export default {
// ============================================================================
'New model configurations are available for Bailian Coding Plan (China). Update now?':
'Bailian Coding Plan (中国) の新しいモデル設定が利用可能です。今すぐ更新しますか?',
- 'New model configurations are available for Coding Plan (Global/Intl). Update now?':
- 'Coding Plan (グローバル/国際) の新しいモデル設定が利用可能です。今すぐ更新しますか?',
+ 'New model configurations are available for Bailian Coding Plan (Global/Intl). Update now?':
+ 'Bailian Coding Plan (グローバル/国際) の新しいモデル設定が利用可能です。今すぐ更新しますか?',
'{{region}} configuration updated successfully. New models are now available.':
'{{region}} の設定が正常に更新されました。新しいモデルが利用可能になりました。',
'Authenticated successfully with {{region}}. API key is stored in settings.env.':
'{{region}} での認証に成功しました。APIキーは settings.env に保存されています。',
- 'Coding Plan (Global/Intl)': 'Coding Plan (グローバル/国際)',
};
diff --git a/packages/cli/src/i18n/locales/pt.js b/packages/cli/src/i18n/locales/pt.js
index 3519fadf2..1720a891a 100644
--- a/packages/cli/src/i18n/locales/pt.js
+++ b/packages/cli/src/i18n/locales/pt.js
@@ -1432,11 +1432,11 @@ export default {
// ============================================================================
'Coding Plan': 'Coding Plan',
'Coding Plan (Bailian, China)': 'Coding Plan (Bailian, China)',
- 'Coding Plan (Bailian, Global/Intl)': 'Coding Plan (Bailian, Global/Intl)',
+ 'Bailian Coding Plan (Global/Intl)': 'Bailian Coding Plan (Global/Intl)',
"Paste your api key of Bailian Coding Plan and you're all set!":
'Cole sua chave de API do Bailian Coding Plan e pronto!',
- "Paste your api key of Coding Plan (Global/Intl) and you're all set!":
- 'Cole sua chave de API do Coding Plan (Global/Intl) e pronto!',
+ "Paste your api key of Bailian Coding Plan (Global/Intl) and you're all set!":
+ 'Cole sua chave de API do Bailian Coding Plan (Global/Intl) e pronto!',
Custom: 'Personalizado',
'More instructions about configuring `modelProviders` manually.':
'Mais instruções sobre como configurar `modelProviders` manualmente.',
@@ -1452,11 +1452,10 @@ export default {
// ============================================================================
'New model configurations are available for Bailian Coding Plan (China). Update now?':
'Novas configurações de modelo estão disponíveis para o Bailian Coding Plan (China). Atualizar agora?',
- 'New model configurations are available for Coding Plan (Global/Intl). Update now?':
- 'Novas configurações de modelo estão disponíveis para o Coding Plan (Global/Intl). Atualizar agora?',
+ 'New model configurations are available for Bailian Coding Plan (Global/Intl). Update now?':
+ 'Novas configurações de modelo estão disponíveis para o Bailian Coding Plan (Global/Intl). Atualizar agora?',
'{{region}} configuration updated successfully. New models are now available.':
'Configuração do {{region}} atualizada com sucesso. Novos modelos agora estão disponíveis.',
'Authenticated successfully with {{region}}. API key is stored in settings.env.':
'Autenticado com sucesso com {{region}}. A chave de API está armazenada em settings.env.',
- 'Coding Plan (Global/Intl)': 'Coding Plan (Global/Intl)',
};
diff --git a/packages/cli/src/i18n/locales/ru.js b/packages/cli/src/i18n/locales/ru.js
index 6854cb1e9..49a5c7226 100644
--- a/packages/cli/src/i18n/locales/ru.js
+++ b/packages/cli/src/i18n/locales/ru.js
@@ -1422,12 +1422,12 @@ export default {
// ============================================================================
'Coding Plan': 'Coding Plan',
'Coding Plan (Bailian, China)': 'Coding Plan (Bailian, Китай)',
- 'Coding Plan (Bailian, Global/Intl)':
- 'Coding Plan (Bailian, Глобальный/Международный)',
+ 'Bailian Coding Plan (Global/Intl)':
+ 'Bailian Coding Plan (Глобальный/Международный)',
"Paste your api key of Bailian Coding Plan and you're all set!":
'Вставьте ваш API-ключ Bailian Coding Plan и всё готово!',
- "Paste your api key of Coding Plan (Global/Intl) and you're all set!":
- 'Вставьте ваш API-ключ Coding Plan (Глобальный/Международный) и всё готово!',
+ "Paste your api key of Bailian Coding Plan (Global/Intl) and you're all set!":
+ 'Вставьте ваш API-ключ Bailian Coding Plan (Глобальный/Международный) и всё готово!',
Custom: 'Пользовательский',
'More instructions about configuring `modelProviders` manually.':
'Дополнительные инструкции по ручной настройке `modelProviders`.',
@@ -1442,11 +1442,10 @@ export default {
// ============================================================================
'New model configurations are available for Bailian Coding Plan (China). Update now?':
'Доступны новые конфигурации моделей для Bailian Coding Plan (Китай). Обновить сейчас?',
- 'New model configurations are available for Coding Plan (Global/Intl). Update now?':
- 'Доступны новые конфигурации моделей для Coding Plan (Глобальный/Международный). Обновить сейчас?',
+ 'New model configurations are available for Bailian Coding Plan (Global/Intl). Update now?':
+ 'Доступны новые конфигурации моделей для Bailian Coding Plan (Глобальный/Международный). Обновить сейчас?',
'{{region}} configuration updated successfully. New models are now available.':
'Конфигурация {{region}} успешно обновлена. Новые модели теперь доступны.',
'Authenticated successfully with {{region}}. API key is stored in settings.env.':
'Успешная аутентификация с {{region}}. API-ключ сохранён в settings.env.',
- 'Coding Plan (Global/Intl)': 'Coding Plan (Глобальный/Международный)',
};
diff --git a/packages/cli/src/i18n/locales/zh.js b/packages/cli/src/i18n/locales/zh.js
index d8c434e4b..a1d6651ef 100644
--- a/packages/cli/src/i18n/locales/zh.js
+++ b/packages/cli/src/i18n/locales/zh.js
@@ -1254,11 +1254,11 @@ export default {
'API-KEY': 'API-KEY',
'Coding Plan': 'Coding Plan',
'Coding Plan (Bailian, China)': 'Coding Plan (百炼, 中国)',
- 'Coding Plan (Bailian, Global/Intl)': 'Coding Plan (百炼, 全球/国际)',
+ 'Bailian Coding Plan (Global/Intl)': 'Bailian Coding Plan (百炼, 全球/国际)',
"Paste your api key of Bailian Coding Plan and you're all set!":
'粘贴您的百炼 Coding Plan API Key,即可完成设置!',
- "Paste your api key of Coding Plan (Global/Intl) and you're all set!":
- '粘贴您的 Coding Plan (全球/国际) API Key,即可完成设置!',
+ "Paste your api key of Bailian Coding Plan (Global/Intl) and you're all set!":
+ '粘贴您的 Bailian Coding Plan (全球/国际) API Key,即可完成设置!',
Custom: '自定义',
'More instructions about configuring `modelProviders` manually.':
'关于手动配置 `modelProviders` 的更多说明。',
@@ -1271,11 +1271,10 @@ export default {
// ============================================================================
'New model configurations are available for Bailian Coding Plan (China). Update now?':
'百炼 Coding Plan (中国) 有新的模型配置可用。是否立即更新?',
- 'New model configurations are available for Coding Plan (Global/Intl). Update now?':
- 'Coding Plan (全球/国际) 有新的模型配置可用。是否立即更新?',
+ 'New model configurations are available for Bailian Coding Plan (Global/Intl). Update now?':
+ 'Bailian Coding Plan (全球/国际) 有新的模型配置可用。是否立即更新?',
'{{region}} configuration updated successfully. New models are now available.':
'{{region}} 配置更新成功。新模型现已可用。',
'Authenticated successfully with {{region}}. API key is stored in settings.env.':
'成功通过 {{region}} 认证。API Key 已存储在 settings.env 中。',
- 'Coding Plan (Global/Intl)': 'Coding Plan (全球/国际)',
};
diff --git a/packages/cli/src/ui/auth/AuthDialog.tsx b/packages/cli/src/ui/auth/AuthDialog.tsx
index 24263f13a..8c9a0aba7 100644
--- a/packages/cli/src/ui/auth/AuthDialog.tsx
+++ b/packages/cli/src/ui/auth/AuthDialog.tsx
@@ -80,7 +80,7 @@ export function AuthDialog(): React.JSX.Element {
},
{
key: 'coding-plan-intl',
- label: t('Coding Plan (Bailian, Global/Intl)'),
+ label: t('Bailian Coding Plan (Global/Intl)'),
value: 'coding-plan-intl' as ApiKeySubMode,
},
{
@@ -259,10 +259,12 @@ export function AuthDialog(): React.JSX.Element {
- {apiKeySubItems[apiKeySubModeIndex]?.value === 'coding-plan'
- ? t("Paste your api key of Bailian Coding Plan and you're all set!")
- : t(
+ {apiKeySubItems[apiKeySubModeIndex]?.value === 'custom'
+ ? t(
'More instructions about configuring `modelProviders` manually.',
+ )
+ : t(
+ "Paste your api key of Bailian Coding Plan and you're all set!",
)}
diff --git a/packages/cli/src/ui/auth/useAuth.ts b/packages/cli/src/ui/auth/useAuth.ts
index 74cd2e8de..bb05172aa 100644
--- a/packages/cli/src/ui/auth/useAuth.ts
+++ b/packages/cli/src/ui/auth/useAuth.ts
@@ -33,6 +33,7 @@ import {
getCodingPlanConfig,
isCodingPlanConfig,
CodingPlanRegion,
+ CODING_PLAN_ENV_KEY,
} from '../../constants/codingPlan.js';
export type { QwenAuthState } from '../hooks/useQwenAuth.js';
@@ -298,23 +299,22 @@ export const useAuthCommand = (
setAuthError(null);
// Get configuration based on region
- const codingPlanConfig = getCodingPlanConfig(region);
- const { template, envKey, version } = codingPlanConfig;
+ const { template, version, regionName } = getCodingPlanConfig(region);
// Get persist scope
const persistScope = getPersistScopeForModelSelection(settings);
- // Store api-key in settings.env
- settings.setValue(persistScope, `env.${envKey}`, apiKey);
+ // Store api-key in settings.env (unified env key)
+ settings.setValue(persistScope, `env.${CODING_PLAN_ENV_KEY}`, apiKey);
// Sync to process.env immediately so refreshAuth can read the apiKey
- process.env[envKey] = apiKey;
+ process.env[CODING_PLAN_ENV_KEY] = apiKey;
// Generate model configs from template
const newConfigs: ProviderModelConfig[] = template.map(
(templateConfig) => ({
...templateConfig,
- envKey,
+ envKey: CODING_PLAN_ENV_KEY,
}),
);
@@ -324,14 +324,9 @@ export const useAuthCommand = (
settings.merged.modelProviders as ModelProvidersConfig | undefined
)?.[AuthType.USE_OPENAI] || [];
- // Identify Coding Plan configs by baseUrl + envKey for the given region
- // Remove existing Coding Plan configs to ensure template changes are applied
- const checkIsCodingPlanConfig = (config: ProviderModelConfig) =>
- isCodingPlanConfig(config.baseUrl, config.envKey, region);
-
- // Filter out existing Coding Plan configs for this region, keep user custom configs
+ // Filter out all existing Coding Plan configs (mutually exclusive)
const nonCodingPlanConfigs = existingConfigs.filter(
- (existing) => !checkIsCodingPlanConfig(existing),
+ (existing) => !isCodingPlanConfig(existing.baseUrl, existing.envKey),
);
// Add new Coding Plan configs at the beginning
@@ -351,13 +346,11 @@ export const useAuthCommand = (
AuthType.USE_OPENAI,
);
- // Persist coding plan version for future update detection
- // Store version with region suffix to distinguish between China and Intl versions
- const versionKey =
- region === CodingPlanRegion.GLOBAL
- ? 'codingPlan.versionIntl'
- : 'codingPlan.version';
- settings.setValue(persistScope, versionKey, version);
+ // Persist coding plan region
+ settings.setValue(persistScope, 'codingPlan.region', region);
+
+ // Persist coding plan version (single field for backward compatibility)
+ settings.setValue(persistScope, 'codingPlan.version', version);
// If there are configs, use the first one as the model
if (updatedConfigs.length > 0 && updatedConfigs[0]?.id) {
@@ -387,16 +380,12 @@ export const useAuthCommand = (
onAuthChange?.();
// Add success message
- const regionLabel =
- region === CodingPlanRegion.GLOBAL
- ? 'Coding Plan (Global/Intl)'
- : 'Coding Plan';
addItem(
{
type: MessageType.INFO,
text: t(
'Authenticated successfully with {{region}}. API key is stored in settings.env.',
- { region: regionLabel },
+ { region: regionName },
),
},
Date.now(),
diff --git a/packages/cli/src/ui/hooks/useCodingPlanUpdates.test.ts b/packages/cli/src/ui/hooks/useCodingPlanUpdates.test.ts
index 6a6a67ea6..b2fe21e98 100644
--- a/packages/cli/src/ui/hooks/useCodingPlanUpdates.test.ts
+++ b/packages/cli/src/ui/hooks/useCodingPlanUpdates.test.ts
@@ -9,14 +9,15 @@ import { renderHook, waitFor } from '@testing-library/react';
import { useCodingPlanUpdates } from './useCodingPlanUpdates.js';
import {
CODING_PLAN_ENV_KEY,
- CODING_PLAN_INTL_ENV_KEY,
- CODING_PLAN_BASE_URL,
- CODING_PLAN_INTL_BASE_URL,
- CODING_PLAN_VERSION,
- CODING_PLAN_INTL_VERSION,
+ getCodingPlanConfig,
+ CodingPlanRegion,
} from '../../constants/codingPlan.js';
import { AuthType } from '@qwen-code/qwen-code-core';
+// Get region configs for testing
+const chinaConfig = getCodingPlanConfig(CodingPlanRegion.CHINA);
+const globalConfig = getCodingPlanConfig(CodingPlanRegion.GLOBAL);
+
describe('useCodingPlanUpdates', () => {
const mockSettings = {
merged: {
@@ -39,7 +40,6 @@ describe('useCodingPlanUpdates', () => {
beforeEach(() => {
vi.clearAllMocks();
delete process.env[CODING_PLAN_ENV_KEY];
- delete process.env[CODING_PLAN_INTL_ENV_KEY];
});
describe('version comparison', () => {
@@ -57,23 +57,10 @@ describe('useCodingPlanUpdates', () => {
expect(result.current.codingPlanUpdateRequest).toBeUndefined();
});
- it('should not show update prompt when China versions match', () => {
- mockSettings.merged.codingPlan = { version: CODING_PLAN_VERSION };
-
- const { result } = renderHook(() =>
- useCodingPlanUpdates(
- mockSettings as never,
- mockConfig as never,
- mockAddItem,
- ),
- );
-
- expect(result.current.codingPlanUpdateRequest).toBeUndefined();
- });
-
- it('should not show update prompt when Global versions match', () => {
+ it('should not show update prompt when China region versions match', () => {
mockSettings.merged.codingPlan = {
- versionIntl: CODING_PLAN_INTL_VERSION,
+ region: CodingPlanRegion.CHINA,
+ version: chinaConfig.version,
};
const { result } = renderHook(() =>
@@ -87,8 +74,28 @@ describe('useCodingPlanUpdates', () => {
expect(result.current.codingPlanUpdateRequest).toBeUndefined();
});
- it('should show update prompt when China versions differ', async () => {
- mockSettings.merged.codingPlan = { version: 'old-version-hash' };
+ it('should not show update prompt when Global region versions match', () => {
+ mockSettings.merged.codingPlan = {
+ region: CodingPlanRegion.GLOBAL,
+ version: globalConfig.version,
+ };
+
+ const { result } = renderHook(() =>
+ useCodingPlanUpdates(
+ mockSettings as never,
+ mockConfig as never,
+ mockAddItem,
+ ),
+ );
+
+ expect(result.current.codingPlanUpdateRequest).toBeUndefined();
+ });
+
+ it('should default to China region when region is not specified', async () => {
+ // No region specified, should default to China
+ mockSettings.merged.codingPlan = {
+ version: 'old-version-hash',
+ };
const { result } = renderHook(() =>
useCodingPlanUpdates(
@@ -102,11 +109,17 @@ describe('useCodingPlanUpdates', () => {
expect(result.current.codingPlanUpdateRequest).toBeDefined();
});
- expect(result.current.codingPlanUpdateRequest?.prompt).toContain('China');
+ // Should prompt for China region since it defaults to China
+ expect(result.current.codingPlanUpdateRequest?.prompt).toContain(
+ chinaConfig.regionName,
+ );
});
- it('should show update prompt when Global versions differ', async () => {
- mockSettings.merged.codingPlan = { versionIntl: 'old-version-hash' };
+ it('should show update prompt when China region versions differ', async () => {
+ mockSettings.merged.codingPlan = {
+ region: CodingPlanRegion.CHINA,
+ version: 'old-version-hash',
+ };
const { result } = renderHook(() =>
useCodingPlanUpdates(
@@ -121,19 +134,45 @@ describe('useCodingPlanUpdates', () => {
});
expect(result.current.codingPlanUpdateRequest?.prompt).toContain(
- 'Global',
+ chinaConfig.regionName,
+ );
+ });
+
+ it('should show update prompt when Global region versions differ', async () => {
+ mockSettings.merged.codingPlan = {
+ region: CodingPlanRegion.GLOBAL,
+ version: 'old-version-hash',
+ };
+
+ const { result } = renderHook(() =>
+ useCodingPlanUpdates(
+ mockSettings as never,
+ mockConfig as never,
+ mockAddItem,
+ ),
+ );
+
+ await waitFor(() => {
+ expect(result.current.codingPlanUpdateRequest).toBeDefined();
+ });
+
+ expect(result.current.codingPlanUpdateRequest?.prompt).toContain(
+ globalConfig.regionName,
);
});
});
describe('update execution', () => {
it('should execute China region update when user confirms', async () => {
- mockSettings.merged.codingPlan = { version: 'old-version-hash' };
+ mockSettings.merged.codingPlan = {
+ region: CodingPlanRegion.CHINA,
+ version: 'old-version-hash',
+ };
mockSettings.merged.modelProviders = {
[AuthType.USE_OPENAI]: [
{
id: 'test-model-china-1',
- baseUrl: CODING_PLAN_BASE_URL,
+ baseUrl: chinaConfig.baseUrl,
envKey: CODING_PLAN_ENV_KEY,
},
{
@@ -162,7 +201,7 @@ describe('useCodingPlanUpdates', () => {
// Wait for async update to complete
await waitFor(() => {
- // Should update model providers (at least 2 calls: modelProviders + version)
+ // Should update model providers (at least 2 calls: modelProviders + version + region)
expect(mockSettings.setValue).toHaveBeenCalled();
});
@@ -170,7 +209,14 @@ describe('useCodingPlanUpdates', () => {
expect(mockSettings.setValue).toHaveBeenCalledWith(
expect.anything(),
'codingPlan.version',
- CODING_PLAN_VERSION,
+ chinaConfig.version,
+ );
+
+ // Should update region
+ expect(mockSettings.setValue).toHaveBeenCalledWith(
+ expect.anything(),
+ 'codingPlan.region',
+ CodingPlanRegion.CHINA,
);
// Should reload and refresh auth
@@ -181,20 +227,23 @@ describe('useCodingPlanUpdates', () => {
expect(mockAddItem).toHaveBeenCalledWith(
expect.objectContaining({
type: 'info',
- text: expect.stringContaining('Coding Plan'),
+ text: expect.stringContaining(chinaConfig.regionName),
}),
expect.any(Number),
);
});
it('should execute Global region update when user confirms', async () => {
- mockSettings.merged.codingPlan = { versionIntl: 'old-version-hash' };
+ mockSettings.merged.codingPlan = {
+ region: CodingPlanRegion.GLOBAL,
+ version: 'old-version-hash',
+ };
mockSettings.merged.modelProviders = {
[AuthType.USE_OPENAI]: [
{
id: 'test-model-global-1',
- baseUrl: CODING_PLAN_INTL_BASE_URL,
- envKey: CODING_PLAN_INTL_ENV_KEY,
+ baseUrl: globalConfig.baseUrl,
+ envKey: CODING_PLAN_ENV_KEY,
},
{
id: 'custom-model',
@@ -225,11 +274,18 @@ describe('useCodingPlanUpdates', () => {
expect(mockSettings.setValue).toHaveBeenCalled();
});
- // Should update versionIntl with correct hash
+ // Should update version with correct hash (single version field)
expect(mockSettings.setValue).toHaveBeenCalledWith(
expect.anything(),
- 'codingPlan.versionIntl',
- CODING_PLAN_INTL_VERSION,
+ 'codingPlan.version',
+ globalConfig.version,
+ );
+
+ // Should update region
+ expect(mockSettings.setValue).toHaveBeenCalledWith(
+ expect.anything(),
+ 'codingPlan.region',
+ CodingPlanRegion.GLOBAL,
);
// Should reload and refresh auth
@@ -240,14 +296,17 @@ describe('useCodingPlanUpdates', () => {
expect(mockAddItem).toHaveBeenCalledWith(
expect.objectContaining({
type: 'info',
- text: expect.stringContaining('Global'),
+ text: expect.stringContaining(globalConfig.regionName),
}),
expect.any(Number),
);
});
it('should not execute update when user declines', async () => {
- mockSettings.merged.codingPlan = { version: 'old-version-hash' };
+ mockSettings.merged.codingPlan = {
+ region: CodingPlanRegion.CHINA,
+ version: 'old-version-hash',
+ };
const { result } = renderHook(() =>
useCodingPlanUpdates(
@@ -269,17 +328,22 @@ describe('useCodingPlanUpdates', () => {
expect(mockConfig.reloadModelProvidersConfig).not.toHaveBeenCalled();
});
- it('should only update configs for the specific region', async () => {
- mockSettings.merged.codingPlan = { version: 'old-version-hash' };
- const chinaConfig = {
+ it('should replace all Coding Plan configs during update (mutually exclusive)', async () => {
+ // Since regions are mutually exclusive, when updating one region,
+ // all Coding Plan configs should be replaced (not preserving other region configs)
+ mockSettings.merged.codingPlan = {
+ region: CodingPlanRegion.CHINA,
+ version: 'old-version-hash',
+ };
+ const chinaModelConfig = {
id: 'test-model-china-1',
- baseUrl: CODING_PLAN_BASE_URL,
+ baseUrl: chinaConfig.baseUrl,
envKey: CODING_PLAN_ENV_KEY,
};
- const globalConfig = {
+ const globalModelConfig = {
id: 'test-model-global-1',
- baseUrl: CODING_PLAN_INTL_BASE_URL,
- envKey: CODING_PLAN_INTL_ENV_KEY,
+ baseUrl: globalConfig.baseUrl,
+ envKey: CODING_PLAN_ENV_KEY,
};
const customConfig = {
id: 'custom-model',
@@ -287,7 +351,11 @@ describe('useCodingPlanUpdates', () => {
envKey: 'CUSTOM_API_KEY',
};
mockSettings.merged.modelProviders = {
- [AuthType.USE_OPENAI]: [chinaConfig, globalConfig, customConfig],
+ [AuthType.USE_OPENAI]: [
+ chinaModelConfig,
+ globalModelConfig,
+ customConfig,
+ ],
};
mockConfig.refreshAuth.mockResolvedValue(undefined);
@@ -316,21 +384,21 @@ describe('useCodingPlanUpdates', () => {
(call[1] as string).includes('modelProviders'),
);
- // Should preserve Global config and custom config, only update China configs
expect(modelProvidersCall).toBeDefined();
const updatedConfigs = modelProvidersCall![2] as Array<
Record
>;
- // Should have new China configs + preserved Global config + custom config
- expect(updatedConfigs.length).toBeGreaterThanOrEqual(3);
+ // Should have new China configs + custom config only (global config removed since regions are mutually exclusive)
+ // The template has 2 models, so we expect 2 (from template) + 1 (custom) = 3
+ expect(updatedConfigs.length).toBe(3);
- // Should contain the Global config (not modified)
+ // Should NOT contain the Global config (mutually exclusive)
expect(
updatedConfigs.some(
- (c: Record) => c['id'] === 'test-model-global-1',
+ (c: Record) => c['baseUrl'] === globalConfig.baseUrl,
),
- ).toBe(true);
+ ).toBe(false);
// Should contain the custom config
expect(
@@ -339,13 +407,23 @@ describe('useCodingPlanUpdates', () => {
),
).toBe(true);
+ // All configs should use the unified env key
+ updatedConfigs.forEach((config) => {
+ if (config['envKey'] === CODING_PLAN_ENV_KEY) {
+ expect(config['baseUrl']).toBe(chinaConfig.baseUrl);
+ }
+ });
+
// Should reload and refresh auth
expect(mockConfig.reloadModelProvidersConfig).toHaveBeenCalled();
expect(mockConfig.refreshAuth).toHaveBeenCalledWith(AuthType.USE_OPENAI);
});
it('should preserve non-Coding Plan configs during update', async () => {
- mockSettings.merged.codingPlan = { version: 'old-version-hash' };
+ mockSettings.merged.codingPlan = {
+ region: CodingPlanRegion.CHINA,
+ version: 'old-version-hash',
+ };
const customConfig = {
id: 'custom-model',
baseUrl: 'https://custom.example.com',
@@ -355,7 +433,7 @@ describe('useCodingPlanUpdates', () => {
[AuthType.USE_OPENAI]: [
{
id: 'test-model-china-1',
- baseUrl: CODING_PLAN_BASE_URL,
+ baseUrl: chinaConfig.baseUrl,
envKey: CODING_PLAN_ENV_KEY,
},
customConfig,
@@ -402,12 +480,15 @@ describe('useCodingPlanUpdates', () => {
});
it('should handle update errors gracefully', async () => {
- mockSettings.merged.codingPlan = { version: 'old-version-hash' };
+ mockSettings.merged.codingPlan = {
+ region: CodingPlanRegion.CHINA,
+ version: 'old-version-hash',
+ };
mockSettings.merged.modelProviders = {
[AuthType.USE_OPENAI]: [
{
id: 'test-model-china-1',
- baseUrl: CODING_PLAN_BASE_URL,
+ baseUrl: chinaConfig.baseUrl,
envKey: CODING_PLAN_ENV_KEY,
},
],
@@ -443,7 +524,10 @@ describe('useCodingPlanUpdates', () => {
describe('dismissUpdate', () => {
it('should clear update request when dismissed', async () => {
- mockSettings.merged.codingPlan = { version: 'old-version-hash' };
+ mockSettings.merged.codingPlan = {
+ region: CodingPlanRegion.CHINA,
+ version: 'old-version-hash',
+ };
const { result } = renderHook(() =>
useCodingPlanUpdates(
diff --git a/packages/cli/src/ui/hooks/useCodingPlanUpdates.ts b/packages/cli/src/ui/hooks/useCodingPlanUpdates.ts
index 3d6e6da23..1646b5aef 100644
--- a/packages/cli/src/ui/hooks/useCodingPlanUpdates.ts
+++ b/packages/cli/src/ui/hooks/useCodingPlanUpdates.ts
@@ -11,10 +11,9 @@ import type { LoadedSettings } from '../../config/settings.js';
import { getPersistScopeForModelSelection } from '../../config/modelProvidersScope.js';
import {
isCodingPlanConfig,
- CODING_PLAN_VERSION,
- CODING_PLAN_INTL_VERSION,
getCodingPlanConfig,
CodingPlanRegion,
+ CODING_PLAN_ENV_KEY,
} from '../../constants/codingPlan.js';
import { t } from '../../i18n/index.js';
@@ -43,7 +42,7 @@ export function useCodingPlanUpdates(
/**
* Execute the Coding Plan configuration update.
* Removes old Coding Plan configs and replaces them with new ones from the template.
- * Automatically detects whether the user is using China or Intl version.
+ * Uses the region from settings.codingPlan.region (defaults to CHINA).
*/
const executeUpdate = useCallback(
async (region: CodingPlanRegion = CodingPlanRegion.CHINA) => {
@@ -58,24 +57,23 @@ export function useCodingPlanUpdates(
| undefined
)?.[AuthType.USE_OPENAI] || [];
- // Filter out Coding Plan configs for the given region (keep user custom configs)
+ // Filter out all Coding Plan configs (since they are mutually exclusive)
+ // Keep only non-Coding-Plan user custom configs
const nonCodingPlanConfigs = currentConfigs.filter(
(cfg) =>
!isCodingPlanConfig(
cfg['baseUrl'] as string | undefined,
cfg['envKey'] as string | undefined,
- region,
),
);
- // Get the correct configuration based on region
- const codingPlanConfig = getCodingPlanConfig(region);
- const { template, envKey, version } = codingPlanConfig;
+ // Get the configuration for the current region
+ const { template, version, regionName } = getCodingPlanConfig(region);
// Generate new configs from template
const newConfigs = template.map((templateConfig) => ({
...templateConfig,
- envKey,
+ envKey: CODING_PLAN_ENV_KEY,
}));
// Combine: new Coding Plan configs at the front, user configs preserved
@@ -91,12 +89,11 @@ export function useCodingPlanUpdates(
updatedConfigs,
);
- // Update the version with region-specific key
- const versionKey =
- region === CodingPlanRegion.GLOBAL
- ? 'codingPlan.versionIntl'
- : 'codingPlan.version';
- settings.setValue(persistScope, versionKey, version);
+ // Update the version (single version field for backward compatibility)
+ settings.setValue(persistScope, 'codingPlan.version', version);
+
+ // Update the region
+ settings.setValue(persistScope, 'codingPlan.region', region);
// Hot-reload model providers configuration
const updatedModelProviders = {
@@ -112,16 +109,12 @@ export function useCodingPlanUpdates(
// Refresh auth with the new configuration
await config.refreshAuth(AuthType.USE_OPENAI);
- const regionLabel =
- region === CodingPlanRegion.GLOBAL
- ? 'Coding Plan (Global/Intl)'
- : 'Coding Plan';
addItem(
{
type: 'info',
text: t(
'{{region}} configuration updated successfully. New models are now available.',
- { region: regionLabel },
+ { region: regionName },
),
},
Date.now(),
@@ -148,56 +141,46 @@ export function useCodingPlanUpdates(
/**
* Check for version mismatch and prompt user for update if needed.
+ * Uses the region from settings.codingPlan.region (defaults to CHINA if not set).
*/
const checkForUpdates = useCallback(() => {
const mergedSettings = settings.merged as {
- codingPlan?: { version?: string; versionIntl?: string };
+ codingPlan?: {
+ version?: string;
+ region?: CodingPlanRegion;
+ };
};
- const savedChinaVersion = mergedSettings.codingPlan?.version;
- const savedIntlVersion = mergedSettings.codingPlan?.versionIntl;
+ // Get the region (default to CHINA if not set)
+ const region = mergedSettings.codingPlan?.region ?? CodingPlanRegion.CHINA;
- // Determine which version the user is using based on saved version
- // Check China version first
- if (savedChinaVersion) {
- if (savedChinaVersion !== CODING_PLAN_VERSION) {
- // China version mismatch - prompt for update
- setUpdateRequest({
- prompt: t(
- 'New model configurations are available for Bailian Coding Plan (China). Update now?',
- ),
- onConfirm: async (confirmed: boolean) => {
- setUpdateRequest(undefined);
- if (confirmed) {
- await executeUpdate(CodingPlanRegion.CHINA);
- }
- },
- });
- return;
- }
- }
-
- // Check Intl version
- if (savedIntlVersion) {
- if (savedIntlVersion !== CODING_PLAN_INTL_VERSION) {
- // Intl version mismatch - prompt for update
- setUpdateRequest({
- prompt: t(
- 'New model configurations are available for Coding Plan (Global/Intl). Update now?',
- ),
- onConfirm: async (confirmed: boolean) => {
- setUpdateRequest(undefined);
- if (confirmed) {
- await executeUpdate(CodingPlanRegion.GLOBAL);
- }
- },
- });
- return;
- }
- }
+ // Get the saved version for the current region
+ const savedVersion = mergedSettings.codingPlan?.version;
// If no version is stored, user hasn't used Coding Plan yet - skip check
- return;
+ if (!savedVersion) {
+ return;
+ }
+
+ // Get current version for the region
+ const currentVersion = getCodingPlanConfig(region).version;
+
+ // Check if version matches
+ if (savedVersion !== currentVersion) {
+ const { regionName } = getCodingPlanConfig(region);
+ setUpdateRequest({
+ prompt: t(
+ 'New model configurations are available for {{region}}. Update now?',
+ { region: regionName },
+ ),
+ onConfirm: async (confirmed: boolean) => {
+ setUpdateRequest(undefined);
+ if (confirmed) {
+ await executeUpdate(region);
+ }
+ },
+ });
+ }
}, [settings, executeUpdate]);
// Check for updates on mount