diff --git a/packages/cli/src/constants/codingPlanTemplates.ts b/packages/cli/src/constants/codingPlanTemplates.ts
index 99c034ecc..8cedab91f 100644
--- a/packages/cli/src/constants/codingPlanTemplates.ts
+++ b/packages/cli/src/constants/codingPlanTemplates.ts
@@ -23,15 +23,16 @@ export const CODING_PLAN_ENV_KEY = 'BAILIAN_CODING_PLAN_API_KEY';
export const CODING_PLAN_TEMPLATE: CodingPlanTemplate = [
{
id: 'qwen3-coder-plus',
- name: 'qwen3-coder-plur',
+ name: 'qwen3-coder-plus',
baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',
- description: 'Qwen3 Coder Plus model from Bailian Coding Plan',
+ 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 Thinking model from Bailian Coding Plan',
+ description:
+ 'qwen3 max model from Bailian Coding Plan with thinking enabled',
baseUrl: 'https://coding.dashscope.aliyuncs.com/v1',
envKey: CODING_PLAN_ENV_KEY,
generationConfig: {
diff --git a/packages/cli/src/i18n/locales/de.js b/packages/cli/src/i18n/locales/de.js
index 5466844d8..377d88511 100644
--- a/packages/cli/src/i18n/locales/de.js
+++ b/packages/cli/src/i18n/locales/de.js
@@ -946,6 +946,11 @@ export default {
'Terms of Services and Privacy Notice for Qwen Code':
'Nutzungsbedingungen und Datenschutzhinweis für Qwen Code',
'Qwen OAuth': 'Qwen OAuth',
+ 'Login with QwenChat account to use daily free quota.':
+ 'Melden Sie sich mit Ihrem QwenChat-Konto an, um das tägliche kostenlose Kontingent zu nutzen.',
+ 'API-KEY': 'API-KEY',
+ 'Use coding plan credentials or your own api-keys/providers.':
+ 'Verwenden Sie Coding Plan-Anmeldedaten oder Ihre eigenen API-Schlüssel/Anbieter.',
OpenAI: 'OpenAI',
'Failed to login. Message: {{message}}':
'Anmeldung fehlgeschlagen. Meldung: {{message}}',
@@ -1389,4 +1394,19 @@ export default {
'Verwenden Sie den /model-Befehl, um Ihr bevorzugtes Modell aus der konfigurierten Liste auszuwählen',
'Supported auth types: openai, anthropic, gemini, vertex-ai, etc.':
'Unterstützte Authentifizierungstypen: openai, anthropic, gemini, vertex-ai, usw.',
+
+ // ============================================================================
+ // Auth Dialog - View Titles and Labels
+ // ============================================================================
+ 'Coding Plan': 'Coding Plan',
+ "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!',
+ Custom: 'Benutzerdefiniert',
+ 'More instructions about configuring `modelProviders` manually.':
+ 'Weitere Anweisungen zur manuellen Konfiguration von `modelProviders`.',
+ 'Select API-KEY configuration mode:':
+ 'API-KEY-Konfigurationsmodus auswählen:',
+ '(Press Escape to go back)': '(Escape drücken zum Zurückgehen)',
+ '(Press Enter to submit, Escape to cancel)':
+ '(Enter zum Absenden, Escape zum Abbrechen)',
};
diff --git a/packages/cli/src/i18n/locales/en.js b/packages/cli/src/i18n/locales/en.js
index 78f57abce..9d27a84e9 100644
--- a/packages/cli/src/i18n/locales/en.js
+++ b/packages/cli/src/i18n/locales/en.js
@@ -937,6 +937,11 @@ export default {
'Terms of Services and Privacy Notice for Qwen Code':
'Terms of Services and Privacy Notice for Qwen Code',
'Qwen OAuth': 'Qwen OAuth',
+ 'Login with QwenChat account to use daily free quota.':
+ 'Login with QwenChat account to use daily free quota.',
+ 'API-KEY': 'API-KEY',
+ 'Use coding plan credentials or your own api-keys/providers.':
+ 'Use coding plan credentials or your own api-keys/providers.',
OpenAI: 'OpenAI',
'Failed to login. Message: {{message}}':
'Failed to login. Message: {{message}}',
@@ -1392,9 +1397,12 @@ export default {
// ============================================================================
// Auth Dialog - View Titles and Labels
// ============================================================================
- 'API-KEY': 'API-KEY',
'Coding Plan': 'Coding Plan',
+ "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!",
Custom: 'Custom',
+ 'More instructions about configuring `modelProviders` manually.':
+ 'More instructions about configuring `modelProviders` manually.',
'Select API-KEY configuration mode:': 'Select API-KEY configuration mode:',
'(Press Escape to go back)': '(Press Escape to go back)',
'(Press Enter to submit, Escape to cancel)':
diff --git a/packages/cli/src/i18n/locales/ja.js b/packages/cli/src/i18n/locales/ja.js
index e2c7306b7..6a4f728e9 100644
--- a/packages/cli/src/i18n/locales/ja.js
+++ b/packages/cli/src/i18n/locales/ja.js
@@ -679,6 +679,11 @@ export default {
'Terms of Services and Privacy Notice for Qwen Code':
'Qwen Code の利用規約とプライバシー通知',
'Qwen OAuth': 'Qwen OAuth',
+ 'Login with QwenChat account to use daily free quota.':
+ 'QwenChatアカウントでログインして、毎日の無料クォータをご利用ください。',
+ 'API-KEY': 'API-KEY',
+ 'Use coding plan credentials or your own api-keys/providers.':
+ 'Coding Planの認証情報またはご自身のAPIキー/プロバイダーをご利用ください。',
OpenAI: 'OpenAI',
'Failed to login. Message: {{message}}':
'ログインに失敗しました。メッセージ: {{message}}',
@@ -900,4 +905,18 @@ export default {
'/model コマンドを使用して、設定済みリストからお好みのモデルを選択してください',
'Supported auth types: openai, anthropic, gemini, vertex-ai, etc.':
'サポートされている認証タイプ:openai、anthropic、gemini、vertex-ai など',
+
+ // ============================================================================
+ // Auth Dialog - View Titles and Labels
+ // ============================================================================
+ 'Coding Plan': 'Coding Plan',
+ "Paste your api key of Bailian Coding Plan and you're all set!":
+ 'Bailian Coding PlanのAPIキーを貼り付けるだけで準備完了です!',
+ Custom: 'カスタム',
+ 'More instructions about configuring `modelProviders` manually.':
+ '`modelProviders`を手動で設定する方法の詳細はこちら。',
+ 'Select API-KEY configuration mode:': 'API-KEY設定モードを選択してください:',
+ '(Press Escape to go back)': '(Escapeキーで戻る)',
+ '(Press Enter to submit, Escape to cancel)':
+ '(Enterで送信、Escapeでキャンセル)',
};
diff --git a/packages/cli/src/i18n/locales/pt.js b/packages/cli/src/i18n/locales/pt.js
index 3e0089fa1..96facbe6d 100644
--- a/packages/cli/src/i18n/locales/pt.js
+++ b/packages/cli/src/i18n/locales/pt.js
@@ -958,6 +958,11 @@ export default {
'Terms of Services and Privacy Notice for Qwen Code':
'Termos de Serviço e Aviso de Privacidade do Qwen Code',
'Qwen OAuth': 'Qwen OAuth',
+ 'Login with QwenChat account to use daily free quota.':
+ 'Faça login com sua conta QwenChat para usar a cota gratuita diária.',
+ 'API-KEY': 'API-KEY',
+ 'Use coding plan credentials or your own api-keys/providers.':
+ 'Use credenciais do Coding Plan ou suas próprias chaves API/provedores.',
OpenAI: 'OpenAI',
'Failed to login. Message: {{message}}':
'Falha ao fazer login. Mensagem: {{message}}',
@@ -1403,4 +1408,19 @@ export default {
'Use o comando /model para selecionar seu modelo preferido da lista configurada',
'Supported auth types: openai, anthropic, gemini, vertex-ai, etc.':
'Tipos de autenticação suportados: openai, anthropic, gemini, vertex-ai, etc.',
+
+ // ============================================================================
+ // Auth Dialog - View Titles and Labels
+ // ============================================================================
+ 'Coding Plan': 'Coding Plan',
+ "Paste your api key of Bailian Coding Plan and you're all set!":
+ 'Cole sua chave de API do Bailian Coding Plan e pronto!',
+ Custom: 'Personalizado',
+ 'More instructions about configuring `modelProviders` manually.':
+ 'Mais instruções sobre como configurar `modelProviders` manualmente.',
+ 'Select API-KEY configuration mode:':
+ 'Selecione o modo de configuração da API-KEY:',
+ '(Press Escape to go back)': '(Pressione Escape para voltar)',
+ '(Press Enter to submit, Escape to cancel)':
+ '(Pressione Enter para enviar, Escape para cancelar)',
};
diff --git a/packages/cli/src/i18n/locales/ru.js b/packages/cli/src/i18n/locales/ru.js
index 4bf570351..f783b98b5 100644
--- a/packages/cli/src/i18n/locales/ru.js
+++ b/packages/cli/src/i18n/locales/ru.js
@@ -952,6 +952,11 @@ export default {
'Terms of Services and Privacy Notice for Qwen Code':
'Условия обслуживания и уведомление о конфиденциальности для Qwen Code',
'Qwen OAuth': 'Qwen OAuth',
+ 'Login with QwenChat account to use daily free quota.':
+ 'Войдите с помощью аккаунта QwenChat, чтобы использовать ежедневную бесплатную квоту.',
+ 'API-KEY': 'API-KEY',
+ 'Use coding plan credentials or your own api-keys/providers.':
+ 'Используйте учетные данные Coding Plan или свои собственные API-ключи/провайдеры.',
OpenAI: 'OpenAI',
'Failed to login. Message: {{message}}':
'Не удалось войти. Сообщение: {{message}}',
@@ -1393,4 +1398,18 @@ export default {
'Используйте команду /model, чтобы выбрать предпочитаемую модель из настроенного списка',
'Supported auth types: openai, anthropic, gemini, vertex-ai, etc.':
'Поддерживаемые типы аутентификации: openai, anthropic, gemini, vertex-ai и др.',
+
+ // ============================================================================
+ // Auth Dialog - View Titles and Labels
+ // ============================================================================
+ 'Coding Plan': 'Coding Plan',
+ "Paste your api key of Bailian Coding Plan and you're all set!":
+ 'Вставьте ваш API-ключ Bailian Coding Plan и всё готово!',
+ Custom: 'Пользовательский',
+ 'More instructions about configuring `modelProviders` manually.':
+ 'Дополнительные инструкции по ручной настройке `modelProviders`.',
+ 'Select API-KEY configuration mode:': 'Выберите режим конфигурации API-KEY:',
+ '(Press Escape to go back)': '(Нажмите Escape для возврата)',
+ '(Press Enter to submit, Escape to cancel)':
+ '(Нажмите Enter для отправки, Escape для отмены)',
};
diff --git a/packages/cli/src/i18n/locales/zh.js b/packages/cli/src/i18n/locales/zh.js
index e314d5129..657919326 100644
--- a/packages/cli/src/i18n/locales/zh.js
+++ b/packages/cli/src/i18n/locales/zh.js
@@ -886,6 +886,10 @@ export default {
'Terms of Services and Privacy Notice for Qwen Code':
'Qwen Code 的服务条款和隐私声明',
'Qwen OAuth': 'Qwen OAuth (免费)',
+ 'Login with QwenChat account to use daily free quota.':
+ '使用 QwenChat 账号登录,享受每日免费额度。',
+ 'Use coding plan credentials or your own api-keys/providers.':
+ '使用 Coding Plan 凭证或您自己的 API 密钥/提供商。',
OpenAI: 'OpenAI',
'Failed to login. Message: {{message}}': '登录失败。消息:{{message}}',
'Authentication is enforced to be {{enforcedType}}, but you are currently using {{currentType}}.':
@@ -1230,7 +1234,11 @@ export default {
// ============================================================================
'API-KEY': 'API-KEY',
'Coding Plan': 'Coding Plan',
+ "Paste your api key of Bailian Coding Plan and you're all set!":
+ '粘贴您的百炼 Coding Plan API Key,即可完成设置!',
Custom: '自定义',
+ 'More instructions about configuring `modelProviders` manually.':
+ '关于手动配置 `modelProviders` 的更多说明。',
'Select API-KEY configuration mode:': '选择 API-KEY 配置模式:',
'(Press Escape to go back)': '(按 Escape 键返回)',
'(Press Enter to submit, Escape to cancel)': '(按 Enter 提交,Escape 取消)',
diff --git a/packages/cli/src/ui/auth/AuthDialog.tsx b/packages/cli/src/ui/auth/AuthDialog.tsx
index f06ba360c..32d3af88f 100644
--- a/packages/cli/src/ui/auth/AuthDialog.tsx
+++ b/packages/cli/src/ui/auth/AuthDialog.tsx
@@ -203,6 +203,13 @@ export function AuthDialog(): React.JSX.Element {
}}
/>
+
+
+ {currentSelectedAuthType === AuthType.QWEN_OAUTH
+ ? t('Login with QwenChat account to use daily free quota.')
+ : t('Use coding plan credentials or your own api-keys/providers.')}
+
+
>
);
@@ -225,6 +232,15 @@ 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(
+ 'More instructions about configuring `modelProviders` manually.',
+ )}
+
+
{t('(Press Escape to go back)')}
diff --git a/packages/core/src/models/modelRegistry.test.ts b/packages/core/src/models/modelRegistry.test.ts
index 3a5993603..01ccc8207 100644
--- a/packages/core/src/models/modelRegistry.test.ts
+++ b/packages/core/src/models/modelRegistry.test.ts
@@ -320,4 +320,215 @@ describe('ModelRegistry', () => {
expect(models.find((m) => m.id === 'invalid-model')).toBeUndefined();
});
});
+
+ describe('duplicate model id handling', () => {
+ it('should skip duplicate model ids and use first registered config', () => {
+ const registry = new ModelRegistry({
+ openai: [
+ { id: 'gpt-4', name: 'GPT-4 First', description: 'First config' },
+ { id: 'gpt-4', name: 'GPT-4 Second', description: 'Second config' },
+ { id: 'gpt-3.5', name: 'GPT-3.5' },
+ ],
+ });
+
+ const models = registry.getModelsForAuthType(AuthType.USE_OPENAI);
+ expect(models.length).toBe(2);
+
+ const gpt4 = registry.getModel(AuthType.USE_OPENAI, 'gpt-4');
+ expect(gpt4).toBeDefined();
+ expect(gpt4?.name).toBe('GPT-4 First');
+ expect(gpt4?.description).toBe('First config');
+ });
+
+ it('should handle multiple duplicate ids in same authType', () => {
+ const registry = new ModelRegistry({
+ openai: [
+ { id: 'model-a', name: 'Model A First' },
+ { id: 'model-a', name: 'Model A Second' },
+ { id: 'model-b', name: 'Model B First' },
+ { id: 'model-b', name: 'Model B Second' },
+ { id: 'model-c', name: 'Model C' },
+ ],
+ });
+
+ const models = registry.getModelsForAuthType(AuthType.USE_OPENAI);
+ expect(models.length).toBe(3);
+
+ expect(registry.getModel(AuthType.USE_OPENAI, 'model-a')?.name).toBe(
+ 'Model A First',
+ );
+ expect(registry.getModel(AuthType.USE_OPENAI, 'model-b')?.name).toBe(
+ 'Model B First',
+ );
+ expect(registry.getModel(AuthType.USE_OPENAI, 'model-c')?.name).toBe(
+ 'Model C',
+ );
+ });
+
+ it('should treat same id in different authTypes as different models', () => {
+ const registry = new ModelRegistry({
+ openai: [{ id: 'shared-model', name: 'OpenAI Shared' }],
+ gemini: [{ id: 'shared-model', name: 'Gemini Shared' }],
+ });
+
+ const openaiModel = registry.getModel(
+ AuthType.USE_OPENAI,
+ 'shared-model',
+ );
+ const geminiModel = registry.getModel(
+ AuthType.USE_GEMINI,
+ 'shared-model',
+ );
+
+ expect(openaiModel?.name).toBe('OpenAI Shared');
+ expect(geminiModel?.name).toBe('Gemini Shared');
+ });
+ });
+
+ describe('reloadModels', () => {
+ it('should reload models from new config', () => {
+ const registry = new ModelRegistry({
+ openai: [{ id: 'gpt-4', name: 'GPT-4' }],
+ });
+
+ expect(registry.getModelsForAuthType(AuthType.USE_OPENAI).length).toBe(1);
+ expect(registry.getModel(AuthType.USE_OPENAI, 'gpt-4')).toBeDefined();
+ expect(registry.getModel(AuthType.USE_OPENAI, 'gpt-3.5')).toBeUndefined();
+
+ registry.reloadModels({
+ openai: [{ id: 'gpt-3.5', name: 'GPT-3.5' }],
+ });
+
+ // After reload, only new models should exist
+ expect(registry.getModelsForAuthType(AuthType.USE_OPENAI).length).toBe(1);
+ expect(registry.getModel(AuthType.USE_OPENAI, 'gpt-4')).toBeUndefined();
+ expect(registry.getModel(AuthType.USE_OPENAI, 'gpt-3.5')).toBeDefined();
+ });
+
+ it('should preserve hard-coded qwen-oauth models after reload', () => {
+ const registry = new ModelRegistry({
+ openai: [{ id: 'gpt-4', name: 'GPT-4' }],
+ });
+
+ expect(registry.getModelsForAuthType(AuthType.QWEN_OAUTH).length).toBe(
+ QWEN_OAUTH_MODELS.length,
+ );
+
+ registry.reloadModels({
+ openai: [{ id: 'gpt-3.5', name: 'GPT-3.5' }],
+ });
+
+ // qwen-oauth models should still exist
+ expect(registry.getModelsForAuthType(AuthType.QWEN_OAUTH).length).toBe(
+ QWEN_OAUTH_MODELS.length,
+ );
+ expect(
+ registry.getModel(AuthType.QWEN_OAUTH, 'coder-model'),
+ ).toBeDefined();
+ });
+
+ it('should clear user-configured models when reload with empty config', () => {
+ const registry = new ModelRegistry({
+ openai: [{ id: 'gpt-4', name: 'GPT-4' }],
+ gemini: [{ id: 'gemini-pro', name: 'Gemini Pro' }],
+ });
+
+ expect(registry.getModelsForAuthType(AuthType.USE_OPENAI).length).toBe(1);
+ expect(registry.getModelsForAuthType(AuthType.USE_GEMINI).length).toBe(1);
+
+ registry.reloadModels({});
+
+ // All user-configured models should be cleared
+ expect(registry.getModelsForAuthType(AuthType.USE_OPENAI).length).toBe(0);
+ expect(registry.getModelsForAuthType(AuthType.USE_GEMINI).length).toBe(0);
+
+ // qwen-oauth models should still exist
+ expect(registry.getModelsForAuthType(AuthType.QWEN_OAUTH).length).toBe(
+ QWEN_OAUTH_MODELS.length,
+ );
+ });
+
+ it('should ignore qwen-oauth models in reload config', () => {
+ const registry = new ModelRegistry();
+
+ registry.reloadModels({
+ 'qwen-oauth': [{ id: 'custom-qwen', name: 'Custom Qwen' }],
+ });
+
+ // qwen-oauth should still use hard-coded models
+ const qwenModels = registry.getModelsForAuthType(AuthType.QWEN_OAUTH);
+ expect(qwenModels.length).toBe(QWEN_OAUTH_MODELS.length);
+ expect(qwenModels.find((m) => m.id === 'custom-qwen')).toBeUndefined();
+ });
+
+ it('should handle reload with multiple authTypes', () => {
+ const registry = new ModelRegistry({
+ openai: [{ id: 'gpt-4', name: 'GPT-4' }],
+ });
+
+ registry.reloadModels({
+ openai: [
+ { id: 'gpt-4', name: 'GPT-4 Updated' },
+ { id: 'gpt-3.5', name: 'GPT-3.5' },
+ ],
+ gemini: [{ id: 'gemini-pro', name: 'Gemini Pro' }],
+ });
+
+ const openaiModels = registry.getModelsForAuthType(AuthType.USE_OPENAI);
+ expect(openaiModels.length).toBe(2);
+ expect(registry.getModel(AuthType.USE_OPENAI, 'gpt-4')?.name).toBe(
+ 'GPT-4 Updated',
+ );
+
+ const geminiModels = registry.getModelsForAuthType(AuthType.USE_GEMINI);
+ expect(geminiModels.length).toBe(1);
+ });
+
+ it('should skip invalid authType keys during reload', () => {
+ const registry = new ModelRegistry({
+ openai: [{ id: 'gpt-4', name: 'GPT-4' }],
+ });
+
+ registry.reloadModels({
+ openai: [{ id: 'gpt-3.5', name: 'GPT-3.5' }],
+ 'invalid-key': [{ id: 'invalid-model', name: 'Invalid Model' }],
+ } as unknown as ModelProvidersConfig);
+
+ const openaiModels = registry.getModelsForAuthType(AuthType.USE_OPENAI);
+ expect(openaiModels.length).toBe(1);
+ expect(registry.getModel(AuthType.USE_OPENAI, 'gpt-3.5')).toBeDefined();
+ });
+
+ it('should handle reload with undefined config', () => {
+ const registry = new ModelRegistry({
+ openai: [{ id: 'gpt-4', name: 'GPT-4' }],
+ });
+
+ registry.reloadModels(undefined);
+
+ // All user-configured models should be cleared
+ expect(registry.getModelsForAuthType(AuthType.USE_OPENAI).length).toBe(0);
+ // qwen-oauth models should still exist
+ expect(registry.getModelsForAuthType(AuthType.QWEN_OAUTH).length).toBe(
+ QWEN_OAUTH_MODELS.length,
+ );
+ });
+
+ it('should apply duplicate model id handling during reload', () => {
+ const registry = new ModelRegistry();
+
+ registry.reloadModels({
+ openai: [
+ { id: 'model-a', name: 'Model A First' },
+ { id: 'model-a', name: 'Model A Second' },
+ ],
+ });
+
+ const models = registry.getModelsForAuthType(AuthType.USE_OPENAI);
+ expect(models.length).toBe(1);
+ expect(registry.getModel(AuthType.USE_OPENAI, 'model-a')?.name).toBe(
+ 'Model A First',
+ );
+ });
+ });
});
diff --git a/packages/core/src/models/modelsConfig.test.ts b/packages/core/src/models/modelsConfig.test.ts
index d0bb62f03..03a724829 100644
--- a/packages/core/src/models/modelsConfig.test.ts
+++ b/packages/core/src/models/modelsConfig.test.ts
@@ -1297,4 +1297,213 @@ describe('ModelsConfig', () => {
});
});
});
+
+ describe('reloadModelProvidersConfig', () => {
+ it('should reload model providers configuration', async () => {
+ const modelsConfig = new ModelsConfig({
+ initialAuthType: AuthType.USE_OPENAI,
+ modelProvidersConfig: {
+ openai: [{ id: 'gpt-4', name: 'GPT-4' }],
+ },
+ });
+
+ // Verify initial model
+ await modelsConfig.switchModel(AuthType.USE_OPENAI, 'gpt-4');
+ expect(modelsConfig.getModel()).toBe('gpt-4');
+
+ // Reload with new config
+ modelsConfig.reloadModelProvidersConfig({
+ openai: [{ id: 'gpt-3.5', name: 'GPT-3.5' }],
+ });
+
+ // After reload, old model should not exist
+ expect(
+ modelsConfig.getAllConfiguredModels().find((m) => m.id === 'gpt-4'),
+ ).toBeUndefined();
+ expect(
+ modelsConfig.getAllConfiguredModels().find((m) => m.id === 'gpt-3.5'),
+ ).toBeDefined();
+ });
+
+ it('should preserve current model selection if still available after reload', async () => {
+ const modelsConfig = new ModelsConfig({
+ initialAuthType: AuthType.USE_OPENAI,
+ modelProvidersConfig: {
+ openai: [
+ { id: 'gpt-4', name: 'GPT-4' },
+ { id: 'gpt-3.5', name: 'GPT-3.5' },
+ ],
+ },
+ });
+
+ await modelsConfig.switchModel(AuthType.USE_OPENAI, 'gpt-4');
+ expect(modelsConfig.getModel()).toBe('gpt-4');
+
+ // Reload with config that still includes gpt-4
+ modelsConfig.reloadModelProvidersConfig({
+ openai: [
+ { id: 'gpt-4', name: 'GPT-4 Updated' },
+ { id: 'new-model', name: 'New Model' },
+ ],
+ });
+
+ // Current model should still be available
+ const availableModels = modelsConfig.getAllConfiguredModels();
+ expect(availableModels.find((m) => m.id === 'gpt-4')).toBeDefined();
+ expect(availableModels.find((m) => m.id === 'new-model')).toBeDefined();
+ });
+
+ it('should update available models after reload', async () => {
+ const modelsConfig = new ModelsConfig({
+ initialAuthType: AuthType.USE_OPENAI,
+ modelProvidersConfig: {
+ openai: [{ id: 'gpt-4', name: 'GPT-4' }],
+ },
+ });
+
+ const initialModels = modelsConfig.getAllConfiguredModels();
+ expect(initialModels.some((m) => m.id === 'gpt-4')).toBe(true);
+ expect(initialModels.some((m) => m.id === 'gemini-pro')).toBe(false);
+
+ // Reload with different config
+ modelsConfig.reloadModelProvidersConfig({
+ openai: [{ id: 'gpt-3.5', name: 'GPT-3.5' }],
+ gemini: [{ id: 'gemini-pro', name: 'Gemini Pro' }],
+ });
+
+ const updatedModels = modelsConfig.getAllConfiguredModels();
+ expect(updatedModels.some((m) => m.id === 'gpt-4')).toBe(false);
+ expect(updatedModels.some((m) => m.id === 'gpt-3.5')).toBe(true);
+ expect(updatedModels.some((m) => m.id === 'gemini-pro')).toBe(true);
+ });
+
+ it('should handle reload with empty config', async () => {
+ const modelsConfig = new ModelsConfig({
+ initialAuthType: AuthType.USE_OPENAI,
+ modelProvidersConfig: {
+ openai: [{ id: 'gpt-4', name: 'GPT-4' }],
+ gemini: [{ id: 'gemini-pro', name: 'Gemini Pro' }],
+ },
+ });
+
+ expect(
+ modelsConfig
+ .getAllConfiguredModels()
+ .filter((m) => m.authType !== 'qwen-oauth').length,
+ ).toBeGreaterThan(0);
+
+ // Reload with empty config
+ modelsConfig.reloadModelProvidersConfig({});
+
+ // Only qwen-oauth models should remain
+ const models = modelsConfig.getAllConfiguredModels();
+ expect(models.every((m) => m.authType === 'qwen-oauth')).toBe(true);
+ });
+
+ it('should preserve qwen-oauth models after reload', () => {
+ const modelsConfig = new ModelsConfig({
+ modelProvidersConfig: {
+ openai: [{ id: 'gpt-4', name: 'GPT-4' }],
+ },
+ });
+
+ const initialQwenModels = modelsConfig
+ .getAllConfiguredModels()
+ .filter((m) => m.authType === 'qwen-oauth');
+
+ modelsConfig.reloadModelProvidersConfig({
+ gemini: [{ id: 'gemini-pro', name: 'Gemini Pro' }],
+ });
+
+ // qwen-oauth models should still exist
+ const qwenModelsAfterReload = modelsConfig
+ .getAllConfiguredModels()
+ .filter((m) => m.authType === 'qwen-oauth');
+ expect(qwenModelsAfterReload.length).toBe(initialQwenModels.length);
+ });
+
+ it('should handle reload with undefined config', () => {
+ const modelsConfig = new ModelsConfig({
+ modelProvidersConfig: {
+ openai: [{ id: 'gpt-4', name: 'GPT-4' }],
+ },
+ });
+
+ expect(
+ modelsConfig
+ .getAllConfiguredModels()
+ .filter((m) => m.authType === 'openai').length,
+ ).toBeGreaterThan(0);
+
+ modelsConfig.reloadModelProvidersConfig(undefined);
+
+ // User-configured models should be cleared
+ expect(
+ modelsConfig
+ .getAllConfiguredModels()
+ .filter((m) => m.authType === 'openai').length,
+ ).toBe(0);
+ });
+
+ it('should support multiple reloads', () => {
+ const modelsConfig = new ModelsConfig();
+
+ // First reload
+ modelsConfig.reloadModelProvidersConfig({
+ openai: [{ id: 'model-v1', name: 'Model V1' }],
+ });
+ expect(
+ modelsConfig.getAllConfiguredModels().some((m) => m.id === 'model-v1'),
+ ).toBe(true);
+
+ // Second reload
+ modelsConfig.reloadModelProvidersConfig({
+ openai: [{ id: 'model-v2', name: 'Model V2' }],
+ });
+ expect(
+ modelsConfig.getAllConfiguredModels().some((m) => m.id === 'model-v1'),
+ ).toBe(false);
+ expect(
+ modelsConfig.getAllConfiguredModels().some((m) => m.id === 'model-v2'),
+ ).toBe(true);
+
+ // Third reload with empty config
+ modelsConfig.reloadModelProvidersConfig({});
+ expect(
+ modelsConfig.getAllConfiguredModels().some((m) => m.id === 'model-v2'),
+ ).toBe(false);
+ });
+
+ it('should handle complex multi-authType reload', async () => {
+ const modelsConfig = new ModelsConfig({
+ initialAuthType: AuthType.USE_OPENAI,
+ modelProvidersConfig: {
+ openai: [
+ { id: 'gpt-4', name: 'GPT-4' },
+ { id: 'gpt-3.5', name: 'GPT-3.5' },
+ ],
+ gemini: [{ id: 'gemini-pro', name: 'Gemini Pro' }],
+ },
+ });
+
+ // Reload with completely different config
+ modelsConfig.reloadModelProvidersConfig({
+ openai: [{ id: 'new-openai', name: 'New OpenAI' }],
+ anthropic: [{ id: 'claude', name: 'Claude' }],
+ gemini: [{ id: 'gemini-ultra', name: 'Gemini Ultra' }],
+ });
+
+ const allModels = modelsConfig.getAllConfiguredModels();
+
+ // Old models should be gone
+ expect(allModels.some((m) => m.id === 'gpt-4')).toBe(false);
+ expect(allModels.some((m) => m.id === 'gpt-3.5')).toBe(false);
+ expect(allModels.some((m) => m.id === 'gemini-pro')).toBe(false);
+
+ // New models should exist
+ expect(allModels.some((m) => m.id === 'new-openai')).toBe(true);
+ expect(allModels.some((m) => m.id === 'claude')).toBe(true);
+ expect(allModels.some((m) => m.id === 'gemini-ultra')).toBe(true);
+ });
+ });
});