From 9aa53a65a215878e344ec363f43c8232aa441e39 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Sun, 19 Jul 2026 16:21:59 +0800 Subject: [PATCH] Add NVIDIA NIM API provider support Register NVIDIA NIM as a built-in OpenAI-compatible chat provider and expose selected featured model presets. Wire shared API-key storage, request routing, setup links, and focused coverage for the NVIDIA hosted endpoint. --- src/background/index.mjs | 2 + src/config/index.mjs | 48 +++++++++++++++++++ src/config/openai-provider-mappings.mjs | 2 + src/popup/sections/GeneralPart.jsx | 2 + src/services/apis/provider-registry.mjs | 9 ++++ tests/unit/config/config-predicates.test.mjs | 9 ++++ .../services/apis/provider-registry.test.mjs | 11 +++++ .../unit/services/apis/thin-adapters.test.mjs | 10 ++++ 8 files changed, 93 insertions(+) diff --git a/src/background/index.mjs b/src/background/index.mjs index 89a1ea048..97dc9462b 100644 --- a/src/background/index.mjs +++ b/src/background/index.mjs @@ -30,6 +30,7 @@ import { isUsingOpenRouterApiModel, isUsingAimlApiModel, isUsingDeepSeekApiModel, + isUsingNvidiaNimApiModel, isUsingGoogleApiModel, } from '../config/index.mjs' import '../_locales/i18n' @@ -398,6 +399,7 @@ function isUsingOpenAICompatibleApiSession(session) { isUsingMoonshotApiModel(session) || isUsingChatGLMApiModel(session) || isUsingDeepSeekApiModel(session) || + isUsingNvidiaNimApiModel(session) || isUsingOllamaApiModel(session) || isUsingOpenRouterApiModel(session) || isUsingAimlApiModel(session) || diff --git a/src/config/index.mjs b/src/config/index.mjs index e1a22f822..7fa7f8c80 100644 --- a/src/config/index.mjs +++ b/src/config/index.mjs @@ -129,6 +129,15 @@ export const deepSeekApiModelKeys = [ 'deepseek_v4_flash', 'deepseek_v4_pro', ] +export const nvidiaNimApiModelKeys = [ + 'nvidiaNim_nemotron_3_super', + 'nvidiaNim_nemotron_3_ultra', + 'nvidiaNim_deepseek_v4_flash', + 'nvidiaNim_deepseek_v4_pro', + 'nvidiaNim_glm_5_2', + 'nvidiaNim_inkling', + 'nvidiaNim_minimax_m3', +] export const openRouterApiModelKeys = [ 'openRouter_auto', 'openRouter_free', @@ -249,6 +258,10 @@ export const ModelGroups = { value: githubThirdPartyApiModelKeys, desc: 'Github Third Party Waylaidwanderer (API)', }, + nvidiaNimApiModelKeys: { + value: nvidiaNimApiModelKeys, + desc: 'NVIDIA NIM (API)', + }, deepSeekApiModelKeys: { value: deepSeekApiModelKeys, desc: 'DeepSeek (API)', @@ -415,6 +428,35 @@ export const Models = { desc: 'Kimi.Moonshot (128k)', }, + nvidiaNim_nemotron_3_super: { + value: 'nvidia/nemotron-3-super-120b-a12b', + desc: 'NVIDIA NIM (Nemotron-3-Super)', + }, + nvidiaNim_nemotron_3_ultra: { + value: 'nvidia/nemotron-3-ultra-550b-a55b', + desc: 'NVIDIA NIM (Nemotron-3-Ultra)', + }, + nvidiaNim_deepseek_v4_flash: { + value: 'deepseek-ai/deepseek-v4-flash', + desc: 'NVIDIA NIM (DeepSeek V4 Flash)', + }, + nvidiaNim_deepseek_v4_pro: { + value: 'deepseek-ai/deepseek-v4-pro', + desc: 'NVIDIA NIM (DeepSeek V4 Pro)', + }, + nvidiaNim_glm_5_2: { + value: 'z-ai/glm-5.2', + desc: 'NVIDIA NIM (GLM-5.2)', + }, + nvidiaNim_inkling: { + value: 'thinkingmachines/inkling', + desc: 'NVIDIA NIM (Inkling)', + }, + nvidiaNim_minimax_m3: { + value: 'minimaxai/minimax-m3', + desc: 'NVIDIA NIM (MiniMax M3)', + }, + deepseek_chat: { value: 'deepseek-chat', desc: 'DeepSeek (Chat)', @@ -692,6 +734,7 @@ export const defaultConfig = { chatglmApiKey: '', moonshotApiKey: '', deepSeekApiKey: '', + nvidiaNimApiKey: '', customApiKey: '', @@ -752,6 +795,7 @@ export const defaultConfig = { 'googleGemini3_5Flash', 'openRouter_auto', 'openRouter_free', + 'nvidiaNim_nemotron_3_super', ], customApiModes: [ { @@ -899,6 +943,10 @@ export function isUsingDeepSeekApiModel(configOrSession) { return isInApiModeGroup(deepSeekApiModelKeys, configOrSession) } +export function isUsingNvidiaNimApiModel(configOrSession) { + return isInApiModeGroup(nvidiaNimApiModelKeys, configOrSession) +} + export function isUsingOpenRouterApiModel(configOrSession) { return isInApiModeGroup(openRouterApiModelKeys, configOrSession) } diff --git a/src/config/openai-provider-mappings.mjs b/src/config/openai-provider-mappings.mjs index 57c0485a3..43e4a920c 100644 --- a/src/config/openai-provider-mappings.mjs +++ b/src/config/openai-provider-mappings.mjs @@ -1,6 +1,7 @@ export const LEGACY_API_KEY_FIELD_BY_PROVIDER_ID = { openai: 'apiKey', deepseek: 'deepSeekApiKey', + 'nvidia-nim': 'nvidiaNimApiKey', moonshot: 'moonshotApiKey', openrouter: 'openRouterApiKey', aiml: 'aimlApiKey', @@ -22,6 +23,7 @@ export const OPENAI_COMPATIBLE_GROUP_TO_PROVIDER_ID = { gptApiModelKeys: 'openai', moonshotApiModelKeys: 'moonshot', deepSeekApiModelKeys: 'deepseek', + nvidiaNimApiModelKeys: 'nvidia-nim', openRouterApiModelKeys: 'openrouter', aimlModelKeys: 'aiml', aimlApiModelKeys: 'aiml', diff --git a/src/popup/sections/GeneralPart.jsx b/src/popup/sections/GeneralPart.jsx index bf1798b89..80e319861 100644 --- a/src/popup/sections/GeneralPart.jsx +++ b/src/popup/sections/GeneralPart.jsx @@ -58,6 +58,8 @@ function isUsingSpecialCustomModel(configOrSession) { function getProviderApiKeySetupUrl(providerId) { switch (String(providerId || '').trim()) { + case 'nvidia-nim': + return 'https://build.nvidia.com/settings/keys' case 'openai': return 'https://platform.openai.com/account/api-keys' case 'moonshot': diff --git a/src/services/apis/provider-registry.mjs b/src/services/apis/provider-registry.mjs index 72aa47662..fe45605d7 100644 --- a/src/services/apis/provider-registry.mjs +++ b/src/services/apis/provider-registry.mjs @@ -26,6 +26,14 @@ const BUILTIN_PROVIDER_TEMPLATE = [ builtin: true, enabled: true, }, + { + id: 'nvidia-nim', + name: 'NVIDIA NIM', + baseUrl: 'https://integrate.api.nvidia.com', + chatCompletionsPath: '/v1/chat/completions', + builtin: true, + enabled: true, + }, { id: 'moonshot', name: 'Kimi.Moonshot', @@ -110,6 +118,7 @@ function resolveProviderIdFromLegacyModelName(modelName) { return 'openai' } if (preset.startsWith('deepseek_') || preset === 'deepSeekApiModelKeys') return 'deepseek' + if (preset.startsWith('nvidiaNim_') || preset === 'nvidiaNimApiModelKeys') return 'nvidia-nim' if (preset.startsWith('moonshot_') || preset === 'moonshotApiModelKeys') return 'moonshot' if (preset.startsWith('openRouter_') || preset === 'openRouterApiModelKeys') return 'openrouter' if (preset.startsWith('aiml_') || preset === 'aimlModelKeys' || preset === 'aimlApiModelKeys') { diff --git a/tests/unit/config/config-predicates.test.mjs b/tests/unit/config/config-predicates.test.mjs index a899f31df..d52b3d206 100644 --- a/tests/unit/config/config-predicates.test.mjs +++ b/tests/unit/config/config-predicates.test.mjs @@ -8,6 +8,7 @@ import { gptApiModelKeys, claudeApiModelKeys, openRouterApiModelKeys, + nvidiaNimApiModelKeys, aimlApiModelKeys, isUsingAimlApiModel, isUsingAzureOpenAiApiModel, @@ -19,6 +20,7 @@ import { isUsingCustomModel, isUsingCustomNameOnlyModel, isUsingDeepSeekApiModel, + isUsingNvidiaNimApiModel, isUsingGeminiWebModel, isUsingGithubThirdPartyApiModel, isUsingMoonshotApiModel, @@ -229,6 +231,13 @@ test('isUsingDeepSeekApiModel detects DeepSeek models', () => { assert.equal(isUsingDeepSeekApiModel({ modelName: 'chatgptApi4oMini' }), false) }) +test('isUsingNvidiaNimApiModel accepts exported NVIDIA NIM API model keys', () => { + for (const modelName of nvidiaNimApiModelKeys) { + assert.equal(isUsingNvidiaNimApiModel({ modelName }), true) + } + assert.equal(isUsingNvidiaNimApiModel({ modelName: 'chatgptApi4oMini' }), false) +}) + test('isUsingOpenRouterApiModel matches representative OpenRouter API keys', () => { for (const modelName of representativeOpenRouterApiModelNames) { assert.equal(isUsingOpenRouterApiModel({ modelName }), true) diff --git a/tests/unit/services/apis/provider-registry.test.mjs b/tests/unit/services/apis/provider-registry.test.mjs index 29d949814..87bb88edc 100644 --- a/tests/unit/services/apis/provider-registry.test.mjs +++ b/tests/unit/services/apis/provider-registry.test.mjs @@ -46,6 +46,17 @@ test('resolveProviderIdForSession resolves legacy Google preset keys', () => { assert.equal(resolveProviderIdForSession({ modelName: 'googleApiModelKeys-custom' }), 'google') }) +test('resolveProviderIdForSession resolves legacy NVIDIA NIM preset keys', () => { + assert.equal( + resolveProviderIdForSession({ modelName: 'nvidiaNim_nemotron_3_super' }), + 'nvidia-nim', + ) + assert.equal( + resolveProviderIdForSession({ modelName: 'nvidiaNimApiModelKeys-custom' }), + 'nvidia-nim', + ) +}) + test('resolveProviderIdForSession does not treat raw Gemini model IDs as legacy presets', () => { assert.equal(resolveProviderIdForSession({ modelName: 'gemini-2.5-flash' }), null) }) diff --git a/tests/unit/services/apis/thin-adapters.test.mjs b/tests/unit/services/apis/thin-adapters.test.mjs index 385fd25f7..04dc7cb17 100644 --- a/tests/unit/services/apis/thin-adapters.test.mjs +++ b/tests/unit/services/apis/thin-adapters.test.mjs @@ -42,6 +42,16 @@ const adapters = [ expectedBaseUrl: 'https://api.deepseek.com', expectedApiKey: 'ds-key', }, + { + name: 'nvidia-nim-api', + apiMode: { + groupName: 'nvidiaNimApiModelKeys', + itemName: 'nvidiaNim_nemotron_3_super', + }, + providerId: 'nvidia-nim', + expectedBaseUrl: 'https://integrate.api.nvidia.com/v1', + expectedApiKey: 'nv-key', + }, { name: 'moonshot-api', apiMode: { groupName: 'moonshotApiModelKeys', itemName: 'moonshot_kimi_latest' },