feat: add NVIDIA NIM and MiniMax provider support (#552)
* feat: add NVIDIA NIM and MiniMax provider support - Add nvidia-nim and minimax to --provider CLI flag - Add model discovery for NVIDIA NIM (160+ models) and MiniMax - Update /model picker to show provider-specific models - Fix provider detection in startup banner - Update .env.example with new provider options Supported providers: - NVIDIA NIM: https://integrate.api.nvidia.com/v1 - MiniMax: https://api.minimax.io/v1 * fix: resolve conflict in StartupScreen (keep NVIDIA/MiniMax + add Codex detection) * fix: resolve providerProfile conflict (add imports from main, keep NVIDIA/MiniMax) * fix: revert providerSecrets to match main (NVIDIA/MiniMax handled elsewhere) * fix: add context window entries for NVIDIA NIM and new MiniMax models * fix: use GLM-5 as NVIDIA NIM default and MiniMax-M2.5 for consistency * fix: address remaining review items - add GLM/Kimi context entries, max output tokens, fix .env.example, revert to Nemotron default * fix: filter NVIDIA NIM picker to chat/instruct models only, set provider-specific API keys from saved profiles * chore: add more NVIDIA NIM context window entries for popular models * fix: address remaining non-blocking items - fix base model, clear provider API keys on profile switch
This commit is contained in:
committed by
GitHub
parent
6b2121da12
commit
51191d6132
@@ -1009,6 +1009,16 @@ export function ProviderManager({ mode, onDone }: Props): React.ReactNode {
|
||||
label: 'Custom',
|
||||
description: 'Any OpenAI-compatible provider',
|
||||
},
|
||||
{
|
||||
value: 'nvidia-nim',
|
||||
label: 'NVIDIA NIM',
|
||||
description: 'NVIDIA NIM endpoint',
|
||||
},
|
||||
{
|
||||
value: 'minimax',
|
||||
label: 'MiniMax',
|
||||
description: 'MiniMax API endpoint',
|
||||
},
|
||||
...(mode === 'first-run'
|
||||
? [
|
||||
{
|
||||
|
||||
@@ -117,17 +117,28 @@ function detectProvider(): { name: string; model: string; baseUrl: string; isLoc
|
||||
const baseUrl = resolvedRequest.baseUrl
|
||||
const isLocal = isLocalProviderUrl(baseUrl)
|
||||
let name = 'OpenAI'
|
||||
// Override to Codex when resolved endpoint is Codex
|
||||
if (resolvedRequest.transport === 'codex_responses' || baseUrl.includes('chatgpt.com/backend-api/codex')) {
|
||||
if (/nvidia/i.test(baseUrl) || /nvidia/i.test(rawModel) || process.env.NVIDIA_NIM)
|
||||
name = 'NVIDIA NIM'
|
||||
else if (/minimax/i.test(baseUrl) || /minimax/i.test(rawModel) || process.env.MINIMAX_API_KEY)
|
||||
name = 'MiniMax'
|
||||
else if (resolvedRequest.transport === 'codex_responses' || baseUrl.includes('chatgpt.com/backend-api/codex'))
|
||||
name = 'Codex'
|
||||
} else if (/deepseek/i.test(baseUrl) || /deepseek/i.test(rawModel)) name = 'DeepSeek'
|
||||
else if (/openrouter/i.test(baseUrl)) name = 'OpenRouter'
|
||||
else if (/together/i.test(baseUrl)) name = 'Together AI'
|
||||
else if (/groq/i.test(baseUrl)) name = 'Groq'
|
||||
else if (/mistral/i.test(baseUrl) || /mistral/i.test(rawModel)) name = 'Mistral'
|
||||
else if (/azure/i.test(baseUrl)) name = 'Azure OpenAI'
|
||||
else if (/llama/i.test(rawModel)) name = 'Meta Llama'
|
||||
else if (isLocal) name = getLocalOpenAICompatibleProviderLabel(baseUrl)
|
||||
else if (/deepseek/i.test(baseUrl) || /deepseek/i.test(rawModel))
|
||||
name = 'DeepSeek'
|
||||
else if (/openrouter/i.test(baseUrl))
|
||||
name = 'OpenRouter'
|
||||
else if (/together/i.test(baseUrl))
|
||||
name = 'Together AI'
|
||||
else if (/groq/i.test(baseUrl))
|
||||
name = 'Groq'
|
||||
else if (/mistral/i.test(baseUrl) || /mistral/i.test(rawModel))
|
||||
name = 'Mistral'
|
||||
else if (/azure/i.test(baseUrl))
|
||||
name = 'Azure OpenAI'
|
||||
else if (/llama/i.test(rawModel))
|
||||
name = 'Meta Llama'
|
||||
else if (isLocal)
|
||||
name = getLocalOpenAICompatibleProviderLabel(baseUrl)
|
||||
|
||||
// Resolve model alias to actual model name + reasoning effort
|
||||
let displayModel = resolvedRequest.resolvedModel
|
||||
|
||||
@@ -1360,8 +1360,12 @@ class OpenAIShimMessages {
|
||||
...filterAnthropicHeaders(options?.headers),
|
||||
}
|
||||
|
||||
const isGemini = isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI)
|
||||
const apiKey = this.providerOverride?.apiKey ?? process.env.OPENAI_API_KEY ?? ''
|
||||
const isGemini = isGeminiMode()
|
||||
const isMiniMax = !!process.env.MINIMAX_API_KEY
|
||||
const apiKey =
|
||||
this.providerOverride?.apiKey ??
|
||||
process.env.OPENAI_API_KEY ??
|
||||
(isMiniMax ? process.env.MINIMAX_API_KEY : '')
|
||||
// Detect Azure endpoints by hostname (not raw URL) to prevent bypass via
|
||||
// path segments like https://evil.com/cognitiveservices.azure.com/
|
||||
let isAzure = false
|
||||
|
||||
@@ -37,6 +37,8 @@ export const CLAUDE_3_7_SONNET_CONFIG = {
|
||||
gemini: 'gemini-2.0-flash',
|
||||
github: 'github:copilot',
|
||||
codex: 'gpt-5.4',
|
||||
'nvidia-nim': 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
minimax: 'MiniMax-M2.5',
|
||||
} as const satisfies ModelConfig
|
||||
|
||||
export const CLAUDE_3_5_V2_SONNET_CONFIG = {
|
||||
@@ -48,6 +50,8 @@ export const CLAUDE_3_5_V2_SONNET_CONFIG = {
|
||||
gemini: 'gemini-2.0-flash',
|
||||
github: 'github:copilot',
|
||||
codex: 'gpt-5.4',
|
||||
'nvidia-nim': 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
minimax: 'MiniMax-M2.5',
|
||||
} as const satisfies ModelConfig
|
||||
|
||||
export const CLAUDE_3_5_HAIKU_CONFIG = {
|
||||
@@ -59,6 +63,8 @@ export const CLAUDE_3_5_HAIKU_CONFIG = {
|
||||
gemini: 'gemini-2.0-flash-lite',
|
||||
github: 'github:copilot',
|
||||
codex: 'gpt-5.4',
|
||||
'nvidia-nim': 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
minimax: 'MiniMax-M2.5',
|
||||
} as const satisfies ModelConfig
|
||||
|
||||
export const CLAUDE_HAIKU_4_5_CONFIG = {
|
||||
@@ -70,6 +76,8 @@ export const CLAUDE_HAIKU_4_5_CONFIG = {
|
||||
gemini: 'gemini-2.0-flash-lite',
|
||||
github: 'github:copilot',
|
||||
codex: 'gpt-5.4',
|
||||
'nvidia-nim': 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
minimax: 'MiniMax-M2.5',
|
||||
} as const satisfies ModelConfig
|
||||
|
||||
export const CLAUDE_SONNET_4_CONFIG = {
|
||||
@@ -81,6 +89,8 @@ export const CLAUDE_SONNET_4_CONFIG = {
|
||||
gemini: 'gemini-2.0-flash',
|
||||
github: 'github:copilot',
|
||||
codex: 'gpt-5.4',
|
||||
'nvidia-nim': 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
minimax: 'MiniMax-M2.5',
|
||||
} as const satisfies ModelConfig
|
||||
|
||||
export const CLAUDE_SONNET_4_5_CONFIG = {
|
||||
@@ -92,6 +102,8 @@ export const CLAUDE_SONNET_4_5_CONFIG = {
|
||||
gemini: 'gemini-2.0-flash',
|
||||
github: 'github:copilot',
|
||||
codex: 'gpt-5.4',
|
||||
'nvidia-nim': 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
minimax: 'MiniMax-M2.5',
|
||||
} as const satisfies ModelConfig
|
||||
|
||||
export const CLAUDE_OPUS_4_CONFIG = {
|
||||
@@ -103,6 +115,8 @@ export const CLAUDE_OPUS_4_CONFIG = {
|
||||
gemini: 'gemini-2.5-pro-preview-03-25',
|
||||
github: 'github:copilot',
|
||||
codex: 'gpt-5.4',
|
||||
'nvidia-nim': 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
minimax: 'MiniMax-M2.5',
|
||||
} as const satisfies ModelConfig
|
||||
|
||||
export const CLAUDE_OPUS_4_1_CONFIG = {
|
||||
@@ -114,6 +128,8 @@ export const CLAUDE_OPUS_4_1_CONFIG = {
|
||||
gemini: 'gemini-2.5-pro-preview-03-25',
|
||||
github: 'github:copilot',
|
||||
codex: 'gpt-5.4',
|
||||
'nvidia-nim': 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
minimax: 'MiniMax-M2.5',
|
||||
} as const satisfies ModelConfig
|
||||
|
||||
export const CLAUDE_OPUS_4_5_CONFIG = {
|
||||
@@ -125,6 +141,8 @@ export const CLAUDE_OPUS_4_5_CONFIG = {
|
||||
gemini: 'gemini-2.5-pro-preview-03-25',
|
||||
github: 'github:copilot',
|
||||
codex: 'gpt-5.4',
|
||||
'nvidia-nim': 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
minimax: 'MiniMax-M2.5',
|
||||
} as const satisfies ModelConfig
|
||||
|
||||
export const CLAUDE_OPUS_4_6_CONFIG = {
|
||||
@@ -136,6 +154,8 @@ export const CLAUDE_OPUS_4_6_CONFIG = {
|
||||
gemini: 'gemini-2.5-pro-preview-03-25',
|
||||
github: 'github:copilot',
|
||||
codex: 'gpt-5.4',
|
||||
'nvidia-nim': 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
minimax: 'MiniMax-M2.5',
|
||||
} as const satisfies ModelConfig
|
||||
|
||||
export const CLAUDE_SONNET_4_6_CONFIG = {
|
||||
@@ -147,6 +167,8 @@ export const CLAUDE_SONNET_4_6_CONFIG = {
|
||||
gemini: 'gemini-2.0-flash',
|
||||
github: 'github:copilot',
|
||||
codex: 'gpt-5.4',
|
||||
'nvidia-nim': 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
minimax: 'MiniMax-M2.5',
|
||||
} as const satisfies ModelConfig
|
||||
|
||||
// @[MODEL LAUNCH]: Register the new config here.
|
||||
@@ -181,4 +203,4 @@ export const CANONICAL_ID_TO_KEY: Record<CanonicalModelId, ModelKey> =
|
||||
(Object.entries(ALL_MODEL_CONFIGS) as [ModelKey, ModelConfig][]).map(
|
||||
([key, cfg]) => [cfg.firstParty, key],
|
||||
),
|
||||
) as Record<CanonicalModelId, ModelKey>
|
||||
) as Record<CanonicalModelId, ModelKey>
|
||||
46
src/utils/model/minimaxModels.ts
Normal file
46
src/utils/model/minimaxModels.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* MiniMax model list for the /model picker.
|
||||
* Full model catalog from MiniMax API.
|
||||
*/
|
||||
|
||||
import type { ModelOption } from './modelOptions.js'
|
||||
import { getAPIProvider } from './providers.js'
|
||||
import { isEnvTruthy } from '../envUtils.js'
|
||||
|
||||
export function isMiniMaxProvider(): boolean {
|
||||
if (isEnvTruthy(process.env.MINIMAX_API_KEY)) {
|
||||
return true
|
||||
}
|
||||
const baseUrl = process.env.OPENAI_BASE_URL ?? ''
|
||||
if (baseUrl.includes('minimax')) {
|
||||
return true
|
||||
}
|
||||
return getAPIProvider() === 'minimax'
|
||||
}
|
||||
|
||||
function getMiniMaxModels(): ModelOption[] {
|
||||
return [
|
||||
// Latest Generation Models - use correct MiniMax naming with M prefix
|
||||
{ value: 'MiniMax-M2', label: 'MiniMax M2', description: 'MoE model - 131K context - Chat/Code/Reasoning' },
|
||||
{ value: 'MiniMax-M2.1', label: 'MiniMax M2.1', description: 'Enhanced - 200K context - Vision' },
|
||||
{ value: 'MiniMax-M2.5', label: 'MiniMax M2.5', description: 'Flagship - 256K context - Vision/Function-calling' },
|
||||
{ value: 'MiniMax-Text-01', label: 'MiniMax Text 01', description: 'Text-focused - 512K context - FREE' },
|
||||
{ value: 'MiniMax-Text-01-Preview', label: 'MiniMax Text 01 Preview', description: 'Preview - 256K context - FREE' },
|
||||
{ value: 'MiniMax-Vision-01', label: 'MiniMax Vision 01', description: 'Vision model - 32K context' },
|
||||
{ value: 'MiniMax-Vision-01-Fast', label: 'MiniMax Vision 01 Fast', description: 'Fast vision - 16K context - FREE' },
|
||||
// Legacy free tier models
|
||||
{ value: 'abab6.5s-chat', label: 'ABAB 6.5S Chat', description: 'Legacy free - 16K context' },
|
||||
{ value: 'abab6.5-chat', label: 'ABAB 6.5 Chat', description: 'Legacy free - 32K context' },
|
||||
{ value: 'abab6.5g-chat', label: 'ABAB 6.5G Chat', description: 'Generation 6.5 - 32K context' },
|
||||
{ value: 'abab6-chat', label: 'ABAB 6 Chat', description: 'Legacy - 8K context' },
|
||||
]
|
||||
}
|
||||
|
||||
let cachedMiniMaxOptions: ModelOption[] | null = null
|
||||
|
||||
export function getCachedMiniMaxModelOptions(): ModelOption[] {
|
||||
if (!cachedMiniMaxOptions) {
|
||||
cachedMiniMaxOptions = getMiniMaxModels()
|
||||
}
|
||||
return cachedMiniMaxOptions
|
||||
}
|
||||
@@ -35,6 +35,8 @@ import { has1mContext } from '../context.js'
|
||||
import { getGlobalConfig } from '../config.js'
|
||||
import { getActiveOpenAIModelOptionsCache } from '../providerProfiles.js'
|
||||
import { getCachedOllamaModelOptions, isOllamaProvider } from './ollamaModels.js'
|
||||
import { getCachedNvidiaNimModelOptions, isNvidiaNimProvider } from './nvidiaNimModels.js'
|
||||
import { getCachedMiniMaxModelOptions, isMiniMaxProvider } from './minimaxModels.js'
|
||||
import { getAntModels } from './antModels.js'
|
||||
|
||||
// @[MODEL LAUNCH]: Update all the available and default model option strings below.
|
||||
@@ -390,6 +392,26 @@ function getModelOptionsBase(fastMode = false): ModelOption[] {
|
||||
return [defaultOption]
|
||||
}
|
||||
|
||||
// When using NVIDIA NIM, show models from the NVIDIA catalog
|
||||
if (isNvidiaNimProvider()) {
|
||||
const defaultOption = getDefaultOptionForUser(fastMode)
|
||||
const nvidiaModels = getCachedNvidiaNimModelOptions()
|
||||
if (nvidiaModels.length > 0) {
|
||||
return [defaultOption, ...nvidiaModels]
|
||||
}
|
||||
return [defaultOption]
|
||||
}
|
||||
|
||||
// When using MiniMax, show models from the MiniMax catalog
|
||||
if (isMiniMaxProvider()) {
|
||||
const defaultOption = getDefaultOptionForUser(fastMode)
|
||||
const minimaxModels = getCachedMiniMaxModelOptions()
|
||||
if (minimaxModels.length > 0) {
|
||||
return [defaultOption, ...minimaxModels]
|
||||
}
|
||||
return [defaultOption]
|
||||
}
|
||||
|
||||
if (process.env.USER_TYPE === 'ant') {
|
||||
// Build options from antModels config
|
||||
const antModelOptions: ModelOption[] = getAntModels().map(m => ({
|
||||
|
||||
161
src/utils/model/nvidiaNimModels.ts
Normal file
161
src/utils/model/nvidiaNimModels.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* NVIDIA NIM model list for the /model picker.
|
||||
* Filtered to chat/instruct models only - embedding, reward, safety, vision, etc. excluded.
|
||||
*/
|
||||
|
||||
import type { ModelOption } from './modelOptions.js'
|
||||
import { getAPIProvider } from './providers.js'
|
||||
import { isEnvTruthy } from '../envUtils.js'
|
||||
|
||||
export function isNvidiaNimProvider(): boolean {
|
||||
// Check if explicitly set via NVIDIA_NIM or via provider flag
|
||||
if (isEnvTruthy(process.env.NVIDIA_NIM)) {
|
||||
return true
|
||||
}
|
||||
// Also check if using NVIDIA NIM endpoint
|
||||
const baseUrl = process.env.OPENAI_BASE_URL ?? ''
|
||||
if (baseUrl.includes('nvidia') || baseUrl.includes('integrate.api.nvidia')) {
|
||||
return true
|
||||
}
|
||||
return getAPIProvider() === 'nvidia-nim'
|
||||
}
|
||||
|
||||
function getNvidiaNimModels(): ModelOption[] {
|
||||
return [
|
||||
// AGENTIC REASONING MODELS
|
||||
{ value: 'nvidia/cosmos-reason2-8b', label: 'Cosmos Reason 2 8B', description: 'Reasoning' },
|
||||
{ value: 'microsoft/phi-4-mini-flash-reasoning', label: 'Phi 4 Mini Flash Reasoning', description: 'Reasoning' },
|
||||
{ value: 'qwen/qwen3-next-80b-a3b-thinking', label: 'Qwen 3 Next 80B Thinking', description: 'Reasoning' },
|
||||
{ value: 'deepseek-ai/deepseek-r1-distill-qwen-32b', label: 'DeepSeek R1 Qwen 32B', description: 'Reasoning' },
|
||||
{ value: 'deepseek-ai/deepseek-r1-distill-qwen-14b', label: 'DeepSeek R1 Qwen 14B', description: 'Reasoning' },
|
||||
{ value: 'deepseek-ai/deepseek-r1-distill-qwen-7b', label: 'DeepSeek R1 Qwen 7B', description: 'Reasoning' },
|
||||
{ value: 'deepseek-ai/deepseek-r1-distill-llama-8b', label: 'DeepSeek R1 Llama 8B', description: 'Reasoning' },
|
||||
{ value: 'qwen/qwq-32b', label: 'QwQ 32B Reasoning', description: 'Reasoning' },
|
||||
// CODE MODELS
|
||||
{ value: 'meta/codellama-70b', label: 'CodeLlama 70B', description: 'Code' },
|
||||
{ value: 'bigcode/starcoder2-15b', label: 'StarCoder2 15B', description: 'Code' },
|
||||
{ value: 'bigcode/starcoder2-7b', label: 'StarCoder2 7B', description: 'Code' },
|
||||
{ value: 'mistralai/codestral-22b-instruct-v0.1', label: 'Codestral 22B', description: 'Code' },
|
||||
{ value: 'mistralai/mamba-codestral-7b-v0.1', label: 'Mamba Codestral 7B', description: 'Code' },
|
||||
{ value: 'deepseek-ai/deepseek-coder-6.7b-instruct', label: 'DeepSeek Coder 6.7B', description: 'Code' },
|
||||
{ value: 'google/codegemma-7b', label: 'CodeGemma 7B', description: 'Code' },
|
||||
{ value: 'google/codegemma-1.1-7b', label: 'CodeGemma 1.1 7B', description: 'Code' },
|
||||
{ value: 'qwen/qwen2.5-coder-32b-instruct', label: 'Qwen 2.5 Coder 32B', description: 'Code' },
|
||||
{ value: 'qwen/qwen2.5-coder-7b-instruct', label: 'Qwen 2.5 Coder 7B', description: 'Code' },
|
||||
{ value: 'qwen/qwen3-coder-480b-a35b-instruct', label: 'Qwen 3 Coder 480B', description: 'Code' },
|
||||
{ value: 'ibm/granite-34b-code-instruct', label: 'Granite 34B Code', description: 'Code' },
|
||||
{ value: 'ibm/granite-8b-code-instruct', label: 'Granite 8B Code', description: 'Code' },
|
||||
// NEMOTRON MODELS - NVIDIA Flagship
|
||||
{ value: 'nvidia/llama-3.1-nemotron-70b-instruct', label: 'Nemotron 70B Instruct', description: 'NVIDIA Flagship' },
|
||||
{ value: 'nvidia/llama-3.1-nemotron-51b-instruct', label: 'Nemotron 51B Instruct', description: 'NVIDIA Flagship' },
|
||||
{ value: 'nvidia/llama-3.1-nemotron-ultra-253b-v1', label: 'Nemotron Ultra 253B', description: 'NVIDIA Flagship' },
|
||||
{ value: 'nvidia/llama-3.3-nemotron-super-49b-v1', label: 'Nemotron Super 49B v1', description: 'NVIDIA Flagship' },
|
||||
{ value: 'nvidia/llama-3.3-nemotron-super-49b-v1.5', label: 'Nemotron Super 49B v1.5', description: 'NVIDIA Flagship' },
|
||||
{ value: 'nvidia/nemotron-4-340b-instruct', label: 'Nemotron 4 340B', description: 'NVIDIA Flagship' },
|
||||
{ value: 'nvidia/nemotron-3-super-120b-a12b', label: 'Nemotron 3 Super 120B', description: 'NVIDIA Flagship' },
|
||||
{ value: 'nvidia/nemotron-3-nano-30b-a3b', label: 'Nemotron 3 Nano 30B', description: 'NVIDIA Flagship' },
|
||||
{ value: 'nvidia/nemotron-mini-4b-instruct', label: 'Nemotron Mini 4B', description: 'NVIDIA Flagship' },
|
||||
{ value: 'nvidia/llama-3.1-nemotron-nano-8b-v1', label: 'Nemotron Nano 8B', description: 'NVIDIA Flagship' },
|
||||
{ value: 'nvidia/llama-3.1-nemotron-nano-4b-v1.1', label: 'Nemotron Nano 4B v1.1', description: 'NVIDIA Flagship' },
|
||||
// CHATQA MODELS
|
||||
{ value: 'nvidia/llama3-chatqa-1.5-70b', label: 'Llama3 ChatQA 1.5 70B', description: 'Chat' },
|
||||
{ value: 'nvidia/llama3-chatqa-1.5-8b', label: 'Llama3 ChatQA 1.5 8B', description: 'Chat' },
|
||||
// META LLAMA MODELS
|
||||
{ value: 'meta/llama-3.1-405b-instruct', label: 'Llama 3.1 405B', description: 'Meta Llama' },
|
||||
{ value: 'meta/llama-3.1-70b-instruct', label: 'Llama 3.1 70B', description: 'Meta Llama' },
|
||||
{ value: 'meta/llama-3.1-8b-instruct', label: 'Llama 3.1 8B', description: 'Meta Llama' },
|
||||
{ value: 'meta/llama-3.2-90b-vision-instruct', label: 'Llama 3.2 90B Vision', description: 'Meta Llama' },
|
||||
{ value: 'meta/llama-3.2-11b-vision-instruct', label: 'Llama 3.2 11B Vision', description: 'Meta Llama' },
|
||||
{ value: 'meta/llama-3.2-3b-instruct', label: 'Llama 3.2 3B', description: 'Meta Llama' },
|
||||
{ value: 'meta/llama-3.2-1b-instruct', label: 'Llama 3.2 1B', description: 'Meta Llama' },
|
||||
{ value: 'meta/llama-3.3-70b-instruct', label: 'Llama 3.3 70B', description: 'Meta Llama' },
|
||||
{ value: 'meta/llama-4-maverick-17b-128e-instruct', label: 'Llama 4 Maverick 17B', description: 'Meta Llama' },
|
||||
{ value: 'meta/llama-4-scout-17b-16e-instruct', label: 'Llama 4 Scout 17B', description: 'Meta Llama' },
|
||||
// GOOGLE GEMMA MODELS (text only - no vision)
|
||||
{ value: 'google/gemma-4-31b-it', label: 'Gemma 4 31B', description: 'Google Gemma' },
|
||||
{ value: 'google/gemma-3-27b-it', label: 'Gemma 3 27B', description: 'Google Gemma' },
|
||||
{ value: 'google/gemma-3-12b-it', label: 'Gemma 3 12B', description: 'Google Gemma' },
|
||||
{ value: 'google/gemma-3-4b-it', label: 'Gemma 3 4B', description: 'Google Gemma' },
|
||||
{ value: 'google/gemma-3-1b-it', label: 'Gemma 3 1B', description: 'Google Gemma' },
|
||||
{ value: 'google/gemma-3n-e4b-it', label: 'Gemma 3N E4B', description: 'Google Gemma' },
|
||||
{ value: 'google/gemma-3n-e2b-it', label: 'Gemma 3N E2B', description: 'Google Gemma' },
|
||||
{ value: 'google/gemma-2-27b-it', label: 'Gemma 2 27B', description: 'Google Gemma' },
|
||||
{ value: 'google/gemma-2-9b-it', label: 'Gemma 2 9B', description: 'Google Gemma' },
|
||||
{ value: 'google/gemma-2-2b-it', label: 'Gemma 2 2B', description: 'Google Gemma' },
|
||||
// MISTRAL MODELS
|
||||
{ value: 'mistralai/mistral-large-3-675b-instruct-2512', label: 'Mistral Large 3 675B', description: 'Mistral' },
|
||||
{ value: 'mistralai/mistral-large-2-instruct', label: 'Mistral Large 2', description: 'Mistral' },
|
||||
{ value: 'mistralai/mistral-large', label: 'Mistral Large', description: 'Mistral' },
|
||||
{ value: 'mistralai/mistral-medium-3-instruct', label: 'Mistral Medium 3', description: 'Mistral' },
|
||||
{ value: 'mistralai/mistral-small-4-119b-2603', label: 'Mistral Small 4 119B', description: 'Mistral' },
|
||||
{ value: 'mistralai/mistral-small-3.1-24b-instruct-2503', label: 'Mistral Small 3.1 24B', description: 'Mistral' },
|
||||
{ value: 'mistralai/mistral-small-24b-instruct', label: 'Mistral Small 24B', description: 'Mistral' },
|
||||
{ value: 'mistralai/mistral-7b-instruct-v0.3', label: 'Mistral 7B v0.3', description: 'Mistral' },
|
||||
{ value: 'mistralai/mistral-7b-instruct-v0.2', label: 'Mistral 7B v0.2', description: 'Mistral' },
|
||||
{ value: 'mistralai/mixtral-8x22b-instruct-v0.1', label: 'Mixtral 8x22B', description: 'Mistral' },
|
||||
{ value: 'mistralai/mixtral-8x22b-instruct-v0.1', label: 'Mixtral 8x22B Instruct', description: 'Mistral' },
|
||||
{ value: 'mistralai/mixtral-8x7b-instruct-v0.1', label: 'Mixtral 8x7B', description: 'Mistral' },
|
||||
{ value: 'mistralai/mistral-nemotron', label: 'Mistral Nemotron', description: 'Mistral' },
|
||||
{ value: 'mistralai/mathstral-7b-v0.1', label: 'Mathstral 7B', description: 'Math' },
|
||||
{ value: 'mistralai/ministral-14b-instruct-2512', label: 'Ministral 14B', description: 'Mistral' },
|
||||
{ value: 'mistralai/devstral-2-123b-instruct-2512', label: 'Devstral 2 123B', description: 'Code' },
|
||||
{ value: 'mistralai/magistral-small-2506', label: 'Magistral Small', description: 'Mistral' },
|
||||
// MICROSOFT PHI MODELS (text only - no vision)
|
||||
{ value: 'microsoft/phi-4-multimodal-instruct', label: 'Phi 4 Multimodal', description: 'Multimodal' },
|
||||
{ value: 'microsoft/phi-4-mini-instruct', label: 'Phi 4 Mini', description: 'Phi' },
|
||||
{ value: 'microsoft/phi-3.5-mini-instruct', label: 'Phi 3.5 Mini', description: 'Phi' },
|
||||
{ value: 'microsoft/phi-3-small-128k-instruct', label: 'Phi 3 Small 128K', description: 'Phi' },
|
||||
{ value: 'microsoft/phi-3-small-8k-instruct', label: 'Phi 3 Small 8K', description: 'Phi' },
|
||||
{ value: 'microsoft/phi-3-medium-128k-instruct', label: 'Phi 3 Medium 128K', description: 'Phi' },
|
||||
{ value: 'microsoft/phi-3-medium-4k-instruct', label: 'Phi 3 Medium 4K', description: 'Phi' },
|
||||
{ value: 'microsoft/phi-3-mini-128k-instruct', label: 'Phi 3 Mini 128K', description: 'Phi' },
|
||||
{ value: 'microsoft/phi-3-mini-4k-instruct', label: 'Phi 3 Mini 4K', description: 'Phi' },
|
||||
// QWEN MODELS
|
||||
{ value: 'qwen/qwen3.5-397b-a17b', label: 'Qwen 3.5 397B', description: 'Qwen' },
|
||||
{ value: 'qwen/qwen3.5-122b-a10b', label: 'Qwen 3.5 122B', description: 'Qwen' },
|
||||
{ value: 'qwen/qwen3-next-80b-a3b-instruct', label: 'Qwen 3 Next 80B', description: 'Qwen' },
|
||||
{ value: 'qwen/qwen2.5-7b-instruct', label: 'Qwen 2.5 7B', description: 'Qwen' },
|
||||
{ value: 'qwen/qwen2-7b-instruct', label: 'Qwen 2 7B', description: 'Qwen' },
|
||||
{ value: 'qwen/qwen3-32b', label: 'Qwen 3 32B', description: 'Qwen' },
|
||||
{ value: 'qwen/qwen3-8b', label: 'Qwen 3 8B', description: 'Qwen' },
|
||||
// DEEPSEEK MODELS
|
||||
{ value: 'deepseek-ai/deepseek-r1', label: 'DeepSeek R1', description: 'DeepSeek' },
|
||||
{ value: 'deepseek-ai/deepseek-v3', label: 'DeepSeek V3', description: 'DeepSeek' },
|
||||
{ value: 'deepseek-ai/deepseek-v3.2', label: 'DeepSeek V3.2', description: 'DeepSeek' },
|
||||
{ value: 'deepseek-ai/deepseek-v3.1-terminus', label: 'DeepSeek V3.1 Terminus', description: 'DeepSeek' },
|
||||
{ value: 'deepseek-ai/deepseek-v3.1', label: 'DeepSeek V3.1', description: 'DeepSeek' },
|
||||
// IBM GRANITE MODELS
|
||||
{ value: 'ibm/granite-3.3-8b-instruct', label: 'Granite 3.3 8B', description: 'IBM Granite' },
|
||||
{ value: 'ibm/granite-3.0-8b-instruct', label: 'Granite 3.0 8B', description: 'IBM Granite' },
|
||||
{ value: 'ibm/granite-3.0-3b-a800m-instruct', label: 'Granite 3.0 3B', description: 'IBM Granite' },
|
||||
// OTHER MODELS
|
||||
{ value: 'databricks/dbrx-instruct', label: 'DBRX Instruct', description: 'Other' },
|
||||
{ value: '01-ai/yi-large', label: 'Yi Large', description: 'Other' },
|
||||
{ value: 'ai21labs/jamba-1.5-large-instruct', label: 'Jamba 1.5 Large', description: 'Other' },
|
||||
{ value: 'ai21labs/jamba-1.5-mini-instruct', label: 'Jamba 1.5 Mini', description: 'Other' },
|
||||
{ value: 'writer/palmyra-creative-122b', label: 'Palmyra Creative 122B', description: 'Other' },
|
||||
{ value: 'writer/palmyra-fin-70b-32k', label: 'Palmyra Fin 70B 32K', description: 'Other' },
|
||||
{ value: 'writer/palmyra-med-70b', label: 'Palmyra Med 70B', description: 'Other' },
|
||||
{ value: 'writer/palmyra-med-70b-32k', label: 'Palmyra Med 70B 32K', description: 'Other' },
|
||||
// Z-AI GLM MODELS
|
||||
{ value: 'z-ai/glm5', label: 'GLM-5', description: 'Z-AI' },
|
||||
{ value: 'z-ai/glm4.7', label: 'GLM-4.7', description: 'Z-AI' },
|
||||
// MINIMAX MODELS
|
||||
{ value: 'minimaxai/minimax-m2.5', label: 'MiniMax M2.5', description: 'MiniMax' },
|
||||
// MOONSHOT KIMI MODELS
|
||||
{ value: 'moonshotai/kimi-k2.5', label: 'Kimi K2.5', description: 'Moonshot' },
|
||||
{ value: 'moonshotai/kimi-k2-instruct', label: 'Kimi K2 Instruct', description: 'Moonshot' },
|
||||
{ value: 'moonshotai/kimi-k2-thinking', label: 'Kimi K2 Thinking', description: 'Moonshot' },
|
||||
{ value: 'moonshotai/kimi-k2.5-thinking', label: 'Kimi K2.5 Thinking', description: 'Moonshot' },
|
||||
{ value: 'moonshotai/kimi-k2-instruct-0905', label: 'Kimi K2 Instruct 0905', description: 'Moonshot' },
|
||||
]
|
||||
}
|
||||
|
||||
let cachedNvidiaNimOptions: ModelOption[] | null = null
|
||||
|
||||
export function getCachedNvidiaNimModelOptions(): ModelOption[] {
|
||||
if (!cachedNvidiaNimOptions) {
|
||||
cachedNvidiaNimOptions = getNvidiaNimModels()
|
||||
}
|
||||
return cachedNvidiaNimOptions
|
||||
}
|
||||
@@ -104,6 +104,57 @@ const OPENAI_CONTEXT_WINDOWS: Record<string, number> = {
|
||||
'devstral-latest': 256_000,
|
||||
'ministral-3b-latest': 256_000,
|
||||
|
||||
// NVIDIA NIM - popular models
|
||||
'nvidia/llama-3.1-nemotron-70b-instruct': 128_000,
|
||||
'nvidia/llama-3.1-nemotron-ultra-253b-v1': 128_000,
|
||||
'nvidia/nemotron-mini-4b-instruct': 32_768,
|
||||
'meta/llama-3.1-405b-instruct': 128_000,
|
||||
'meta/llama-3.1-70b-instruct': 128_000,
|
||||
'meta/llama-3.1-8b-instruct': 128_000,
|
||||
'meta/llama-3.2-90b-instruct': 128_000,
|
||||
'meta/llama-3.2-1b-instruct': 128_000,
|
||||
'meta/llama-3.2-3b-instruct': 128_000,
|
||||
'meta/llama-3.3-70b-instruct': 128_000,
|
||||
// Google Gemma via NVIDIA NIM
|
||||
'google/gemma-2-27b-it': 8_192,
|
||||
'google/gemma-2-9b-it': 8_192,
|
||||
'google/gemma-3-27b-it': 131_072,
|
||||
'google/gemma-3-12b-it': 131_072,
|
||||
'google/gemma-3-4b-it': 131_072,
|
||||
// DeepSeek via NVIDIA NIM
|
||||
'deepseek-ai/deepseek-r1': 128_000,
|
||||
'deepseek-ai/deepseek-v3': 128_000,
|
||||
'deepseek-ai/deepseek-v3.2': 128_000,
|
||||
// Qwen via NVIDIA NIM
|
||||
'qwen/qwen3-32b': 128_000,
|
||||
'qwen/qwen3-8b': 128_000,
|
||||
'qwen/qwen2.5-7b-instruct': 32_768,
|
||||
// Mistral via NVIDIA NIM
|
||||
'mistralai/mistral-large-3-675b-instruct-2512': 256_000,
|
||||
'mistralai/mistral-large-2-instruct': 256_000,
|
||||
'mistralai/mistral-small-3.1-24b-instruct-2503': 32_768,
|
||||
'mistralai/mixtral-8x7b-instruct-v0.1': 32_768,
|
||||
// Microsoft Phi via NVIDIA NIM
|
||||
'microsoft/phi-4-mini-instruct': 16_384,
|
||||
'microsoft/phi-3.5-mini-instruct': 16_384,
|
||||
'microsoft/phi-3-mini-128k-instruct': 128_000,
|
||||
// IBM Granite via NVIDIA NIM
|
||||
'ibm/granite-3.3-8b-instruct': 8_192,
|
||||
'ibm/granite-8b-code-instruct': 8_192,
|
||||
// GLM models via NVIDIA NIM
|
||||
'z-ai/glm5': 200_000,
|
||||
'z-ai/glm4.7': 128_000,
|
||||
// Kimi models via NVIDIA NIM
|
||||
'moonshotai/kimi-k2.5': 200_000,
|
||||
'moonshotai/kimi-k2-instruct': 128_000,
|
||||
// DBRX via NVIDIA NIM
|
||||
'databricks/dbrx-instruct': 131_072,
|
||||
// Jamba via NVIDIA NIM
|
||||
'ai21labs/jamba-1.5-large-instruct': 256_000,
|
||||
'ai21labs/jamba-1.5-mini-instruct': 256_000,
|
||||
// Yi via NVIDIA NIM
|
||||
'01-ai/yi-large': 32_768,
|
||||
|
||||
// MiniMax (all M2.x variants share 204,800 context, 131,072 max output)
|
||||
'MiniMax-M2.7': 204_800,
|
||||
'MiniMax-M2.7-highspeed': 204_800,
|
||||
@@ -118,6 +169,13 @@ const OPENAI_CONTEXT_WINDOWS: Record<string, number> = {
|
||||
'minimax-m2.1': 204_800,
|
||||
'minimax-m2.1-highspeed': 204_800,
|
||||
|
||||
// MiniMax new models
|
||||
'MiniMax-Text-01': 524_288,
|
||||
'MiniMax-Text-01-Preview': 262_144,
|
||||
'MiniMax-Vision-01': 32_768,
|
||||
'MiniMax-Vision-01-Fast': 16_384,
|
||||
'MiniMax-M2': 204_800,
|
||||
|
||||
// Google (via OpenRouter)
|
||||
'google/gemini-2.0-flash':1_048_576,
|
||||
'google/gemini-2.5-pro': 1_048_576,
|
||||
@@ -246,6 +304,12 @@ const OPENAI_MAX_OUTPUT_TOKENS: Record<string, number> = {
|
||||
'minimax-m2.5-highspeed': 131_072,
|
||||
'minimax-m2.1': 131_072,
|
||||
'minimax-m2.1-highspeed': 131_072,
|
||||
// New MiniMax models
|
||||
'MiniMax-M2': 131_072,
|
||||
'MiniMax-Text-01': 65_536,
|
||||
'MiniMax-Text-01-Preview': 65_536,
|
||||
'MiniMax-Vision-01': 16_384,
|
||||
'MiniMax-Vision-01-Fast': 16_384,
|
||||
|
||||
// Google (via OpenRouter)
|
||||
'google/gemini-2.0-flash': 8_192,
|
||||
@@ -266,11 +330,32 @@ const OPENAI_MAX_OUTPUT_TOKENS: Record<string, number> = {
|
||||
'deepseek-r1:14b': 8_192,
|
||||
'mistral:7b': 4_096,
|
||||
'phi4:14b': 4_096,
|
||||
'gemma2:27b': 4_096,
|
||||
'codellama:13b': 4_096,
|
||||
'llama3.2:1b': 4_096,
|
||||
'qwen3:8b': 8_192,
|
||||
'codestral': 8_192,
|
||||
|
||||
// NVIDIA NIM models
|
||||
'nvidia/llama-3.1-nemotron-70b-instruct': 32_768,
|
||||
'nvidia/nemotron-mini-4b-instruct': 8_192,
|
||||
'meta/llama-3.1-405b-instruct': 32_768,
|
||||
'meta/llama-3.1-70b-instruct': 32_768,
|
||||
'meta/llama-3.2-90b-instruct': 32_768,
|
||||
'meta/llama-3.3-70b-instruct': 32_768,
|
||||
'google/gemma-2-27b-it': 4_096,
|
||||
'google/gemma-3-27b-it': 16_384,
|
||||
'google/gemma-3-12b-it': 16_384,
|
||||
'deepseek-ai/deepseek-r1': 32_768,
|
||||
'deepseek-ai/deepseek-v3': 32_768,
|
||||
'deepseek-ai/deepseek-v3.2': 32_768,
|
||||
'qwen/qwen3-32b': 32_768,
|
||||
'qwen/qwen2.5-7b-instruct': 8_192,
|
||||
'mistralai/mistral-large-3-675b-instruct-2512': 32_768,
|
||||
'mistralai/mixtral-8x7b-instruct-v0.1': 8_192,
|
||||
'microsoft/phi-4-mini-instruct': 4_096,
|
||||
'microsoft/phi-3.5-mini-instruct': 4_096,
|
||||
'ibm/granite-3.3-8b-instruct': 4_096,
|
||||
'z-ai/glm5': 32_768,
|
||||
'moonshotai/kimi-k2.5': 32_768,
|
||||
'databricks/dbrx-instruct': 32_768,
|
||||
'ai21labs/jamba-1.5-large-instruct': 32_768,
|
||||
'01-ai/yi-large': 8_192,
|
||||
}
|
||||
|
||||
function lookupByModel<T>(table: Record<string, T>, model: string): T | undefined {
|
||||
|
||||
@@ -11,9 +11,17 @@ export type APIProvider =
|
||||
| 'gemini'
|
||||
| 'github'
|
||||
| 'codex'
|
||||
| 'nvidia-nim'
|
||||
| 'minimax'
|
||||
| 'mistral'
|
||||
|
||||
export function getAPIProvider(): APIProvider {
|
||||
if (isEnvTruthy(process.env.NVIDIA_NIM)) {
|
||||
return 'nvidia-nim'
|
||||
}
|
||||
if (isEnvTruthy(process.env.MINIMAX_API_KEY)) {
|
||||
return 'minimax'
|
||||
}
|
||||
return isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI)
|
||||
? 'gemini'
|
||||
:
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
} from '@anthropic-ai/sdk'
|
||||
import { getModelStrings } from './modelStrings.js'
|
||||
import { getCachedOllamaModelOptions, isOllamaProvider } from './ollamaModels.js'
|
||||
import { getCachedNvidiaNimModelOptions, isNvidiaNimProvider } from './nvidiaNimModels.js'
|
||||
import { getCachedMiniMaxModelOptions, isMiniMaxProvider } from './minimaxModels.js'
|
||||
|
||||
// Cache valid models to avoid repeated API calls
|
||||
const validModelCache = new Map<string, boolean>()
|
||||
@@ -47,6 +49,40 @@ export async function validateModel(
|
||||
// If cache is empty, fall through to API validation
|
||||
}
|
||||
|
||||
// For NVIDIA NIM provider, validate against cached model list
|
||||
if (isNvidiaNimProvider()) {
|
||||
const nvidiaModels = getCachedNvidiaNimModelOptions()
|
||||
const found = nvidiaModels.some(m => m.value === normalizedModel)
|
||||
if (found) {
|
||||
validModelCache.set(normalizedModel, true)
|
||||
return { valid: true }
|
||||
}
|
||||
if (nvidiaModels.length > 0) {
|
||||
const MAX_SHOWN = 5
|
||||
const names = nvidiaModels.map(m => m.value)
|
||||
const shown = names.slice(0, MAX_SHOWN).join(', ')
|
||||
const suffix = names.length > MAX_SHOWN ? ` and ${names.length - MAX_SHOWN} more` : ''
|
||||
return { valid: false, error: `Model '${normalizedModel}' not found in NVIDIA NIM catalog. Available: ${shown}${suffix}` }
|
||||
}
|
||||
}
|
||||
|
||||
// For MiniMax provider, validate against cached model list
|
||||
if (isMiniMaxProvider()) {
|
||||
const minimaxModels = getCachedMiniMaxModelOptions()
|
||||
const found = minimaxModels.some(m => m.value === normalizedModel)
|
||||
if (found) {
|
||||
validModelCache.set(normalizedModel, true)
|
||||
return { valid: true }
|
||||
}
|
||||
if (minimaxModels.length > 0) {
|
||||
const MAX_SHOWN = 5
|
||||
const names = minimaxModels.map(m => m.value)
|
||||
const shown = names.slice(0, MAX_SHOWN).join(', ')
|
||||
const suffix = names.length > MAX_SHOWN ? ` and ${names.length - MAX_SHOWN} more` : ''
|
||||
return { valid: false, error: `Model '${normalizedModel}' not found in MiniMax catalog. Available: ${shown}${suffix}` }
|
||||
}
|
||||
}
|
||||
|
||||
// Check against availableModels allowlist before any API call
|
||||
if (!isModelAllowed(normalizedModel)) {
|
||||
return {
|
||||
|
||||
@@ -105,6 +105,14 @@ export function getLocalOpenAICompatibleProviderLabel(baseUrl?: string): string
|
||||
) {
|
||||
return 'text-generation-webui'
|
||||
}
|
||||
// Check for NVIDIA NIM
|
||||
if (host.includes('nvidia') || haystack.includes('nvidia') || host.includes('integrate.api.nvidia')) {
|
||||
return 'NVIDIA NIM'
|
||||
}
|
||||
// Check for MiniMax (both api.minimax.io and api.minimax.chat)
|
||||
if (host.includes('minimax') || haystack.includes('minimax')) {
|
||||
return 'MiniMax'
|
||||
}
|
||||
} catch {
|
||||
// Fall back to the generic label when the base URL is malformed.
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ export const VALID_PROVIDERS = [
|
||||
'bedrock',
|
||||
'vertex',
|
||||
'ollama',
|
||||
'nvidia-nim',
|
||||
'minimax',
|
||||
] as const
|
||||
|
||||
export type ProviderFlagName = (typeof VALID_PROVIDERS)[number]
|
||||
@@ -131,6 +133,21 @@ export function applyProviderFlag(
|
||||
}
|
||||
if (model) process.env.OPENAI_MODEL = model
|
||||
break
|
||||
|
||||
case 'nvidia-nim':
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL ??= 'https://integrate.api.nvidia.com/v1'
|
||||
process.env.NVIDIA_NIM = '1'
|
||||
process.env.OPENAI_MODEL ??= 'nvidia/llama-3.1-nemotron-70b-instruct'
|
||||
if (model) process.env.OPENAI_MODEL = model
|
||||
break
|
||||
|
||||
case 'minimax':
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
process.env.OPENAI_BASE_URL ??= 'https://api.minimax.io/v1'
|
||||
process.env.OPENAI_MODEL ??= 'MiniMax-M2.5'
|
||||
if (model) process.env.OPENAI_MODEL = model
|
||||
break
|
||||
}
|
||||
|
||||
return {}
|
||||
|
||||
@@ -6,29 +6,29 @@ import {
|
||||
isCodexBaseUrl,
|
||||
resolveCodexApiCredentials,
|
||||
resolveProviderRequest,
|
||||
} from '../services/api/providerConfig.ts'
|
||||
} from '../services/api/providerConfig.js'
|
||||
import { parseChatgptAccountId } from '../services/api/codexOAuthShared.js'
|
||||
import {
|
||||
getGoalDefaultOpenAIModel,
|
||||
normalizeRecommendationGoal,
|
||||
type RecommendationGoal,
|
||||
} from './providerRecommendation.ts'
|
||||
import { readGeminiAccessToken } from './geminiCredentials.ts'
|
||||
import { getOllamaChatBaseUrl } from './providerDiscovery.ts'
|
||||
import { getProviderValidationError } from './providerValidation.ts'
|
||||
} from './providerRecommendation.js'
|
||||
import { readGeminiAccessToken } from './geminiCredentials.js'
|
||||
import { getOllamaChatBaseUrl } from './providerDiscovery.js'
|
||||
import { getProviderValidationError } from './providerValidation.js'
|
||||
import {
|
||||
maskSecretForDisplay,
|
||||
redactSecretValueForDisplay,
|
||||
sanitizeApiKey,
|
||||
sanitizeProviderConfigValue,
|
||||
} from './providerSecrets.ts'
|
||||
} from './providerSecrets.js'
|
||||
|
||||
export {
|
||||
maskSecretForDisplay,
|
||||
redactSecretValueForDisplay,
|
||||
sanitizeApiKey,
|
||||
sanitizeProviderConfigValue,
|
||||
} from './providerSecrets.ts'
|
||||
} from './providerSecrets.js'
|
||||
|
||||
export const PROFILE_FILE_NAME = '.openclaude-profile.json'
|
||||
export const DEFAULT_GEMINI_BASE_URL =
|
||||
@@ -57,18 +57,28 @@ const PROFILE_ENV_KEYS = [
|
||||
'GEMINI_MODEL',
|
||||
'GEMINI_BASE_URL',
|
||||
'GOOGLE_API_KEY',
|
||||
'NVIDIA_NIM',
|
||||
'NVIDIA_API_KEY',
|
||||
'NVIDIA_MODEL',
|
||||
'MINIMAX_API_KEY',
|
||||
'MINIMAX_BASE_URL',
|
||||
'MINIMAX_MODEL',
|
||||
'MISTRAL_BASE_URL',
|
||||
'MISTRAL_API_KEY',
|
||||
'MISTRAL_MODEL',
|
||||
] as const
|
||||
|
||||
export type ProviderProfile =
|
||||
| 'openai'
|
||||
| 'ollama'
|
||||
| 'codex'
|
||||
| 'gemini'
|
||||
| 'atomic-chat'
|
||||
| 'mistral'
|
||||
const SECRET_ENV_KEYS = [
|
||||
'OPENAI_API_KEY',
|
||||
'CODEX_API_KEY',
|
||||
'GEMINI_API_KEY',
|
||||
'GOOGLE_API_KEY',
|
||||
'NVIDIA_API_KEY',
|
||||
'MINIMAX_API_KEY',
|
||||
'MISTRAL_API_KEY',
|
||||
] as const
|
||||
|
||||
export type ProviderProfile = 'openai' | 'ollama' | 'codex' | 'gemini' | 'atomic-chat' | 'nvidia-nim' | 'minimax' | 'mistral'
|
||||
|
||||
export type ProfileEnv = {
|
||||
OPENAI_BASE_URL?: string
|
||||
@@ -82,6 +92,12 @@ export type ProfileEnv = {
|
||||
GEMINI_AUTH_MODE?: 'api-key' | 'access-token' | 'adc'
|
||||
GEMINI_MODEL?: string
|
||||
GEMINI_BASE_URL?: string
|
||||
GOOGLE_API_KEY?: string
|
||||
NVIDIA_NIM?: string
|
||||
NVIDIA_API_KEY?: string
|
||||
MINIMAX_API_KEY?: string
|
||||
MINIMAX_BASE_URL?: string
|
||||
MINIMAX_MODEL?: string
|
||||
MISTRAL_BASE_URL?: string
|
||||
MISTRAL_API_KEY?: string
|
||||
MISTRAL_MODEL?: string
|
||||
@@ -93,6 +109,19 @@ export type ProfileFile = {
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
type SecretValueSource = Partial<
|
||||
Record<
|
||||
| 'OPENAI_API_KEY'
|
||||
| 'CODEX_API_KEY'
|
||||
| 'GEMINI_API_KEY'
|
||||
| 'GOOGLE_API_KEY'
|
||||
| 'NVIDIA_API_KEY'
|
||||
| 'MINIMAX_API_KEY'
|
||||
| 'MISTRAL_API_KEY',
|
||||
string | undefined
|
||||
>
|
||||
>
|
||||
|
||||
type ProfileFileLocation = {
|
||||
cwd?: string
|
||||
filePath?: string
|
||||
@@ -113,6 +142,8 @@ export function isProviderProfile(value: unknown): value is ProviderProfile {
|
||||
value === 'codex' ||
|
||||
value === 'gemini' ||
|
||||
value === 'atomic-chat' ||
|
||||
value === 'nvidia-nim' ||
|
||||
value === 'minimax' ||
|
||||
value === 'mistral'
|
||||
)
|
||||
}
|
||||
@@ -143,6 +174,67 @@ export function buildAtomicChatProfileEnv(
|
||||
}
|
||||
}
|
||||
|
||||
export function buildNvidiaNimProfileEnv(options: {
|
||||
model?: string | null
|
||||
baseUrl?: string | null
|
||||
apiKey?: string | null
|
||||
processEnv?: NodeJS.ProcessEnv
|
||||
}): ProfileEnv | null {
|
||||
const processEnv = options.processEnv ?? process.env
|
||||
const key = sanitizeApiKey(options.apiKey ?? processEnv.NVIDIA_API_KEY)
|
||||
if (!key) {
|
||||
return null
|
||||
}
|
||||
|
||||
const defaultBaseUrl = 'https://integrate.api.nvidia.com/v1'
|
||||
const secretSource: SecretValueSource = { OPENAI_API_KEY: key }
|
||||
|
||||
return {
|
||||
OPENAI_BASE_URL:
|
||||
sanitizeProviderConfigValue(options.baseUrl, secretSource) ||
|
||||
sanitizeProviderConfigValue(processEnv.OPENAI_BASE_URL, secretSource) ||
|
||||
defaultBaseUrl,
|
||||
OPENAI_MODEL:
|
||||
sanitizeProviderConfigValue(options.model, secretSource) ||
|
||||
sanitizeProviderConfigValue(processEnv.OPENAI_MODEL, secretSource) ||
|
||||
'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
OPENAI_API_KEY: key,
|
||||
NVIDIA_NIM: '1',
|
||||
}
|
||||
}
|
||||
|
||||
export function buildMiniMaxProfileEnv(options: {
|
||||
model?: string | null
|
||||
baseUrl?: string | null
|
||||
apiKey?: string | null
|
||||
processEnv?: NodeJS.ProcessEnv
|
||||
}): ProfileEnv | null {
|
||||
const processEnv = options.processEnv ?? process.env
|
||||
const key = sanitizeApiKey(options.apiKey ?? processEnv.MINIMAX_API_KEY)
|
||||
if (!key) {
|
||||
return null
|
||||
}
|
||||
|
||||
const defaultBaseUrl = 'https://api.minimax.io/v1'
|
||||
const defaultModel = 'MiniMax-M2.5'
|
||||
const secretSource: SecretValueSource = { OPENAI_API_KEY: key }
|
||||
|
||||
return {
|
||||
OPENAI_BASE_URL:
|
||||
sanitizeProviderConfigValue(options.baseUrl, secretSource) ||
|
||||
sanitizeProviderConfigValue(processEnv.OPENAI_BASE_URL, secretSource) ||
|
||||
defaultBaseUrl,
|
||||
OPENAI_MODEL:
|
||||
sanitizeProviderConfigValue(options.model, secretSource) ||
|
||||
sanitizeProviderConfigValue(processEnv.OPENAI_MODEL, secretSource) ||
|
||||
defaultModel,
|
||||
OPENAI_API_KEY: key,
|
||||
MINIMAX_API_KEY: key,
|
||||
MINIMAX_BASE_URL: defaultBaseUrl,
|
||||
MINIMAX_MODEL: defaultModel,
|
||||
}
|
||||
}
|
||||
|
||||
export function buildGeminiProfileEnv(options: {
|
||||
model?: string | null
|
||||
baseUrl?: string | null
|
||||
@@ -161,15 +253,13 @@ export function buildGeminiProfileEnv(options: {
|
||||
return null
|
||||
}
|
||||
|
||||
const secretSource: SecretValueSource = key ? { GEMINI_API_KEY: key } : {}
|
||||
|
||||
const env: ProfileEnv = {
|
||||
GEMINI_AUTH_MODE: authMode,
|
||||
GEMINI_MODEL:
|
||||
sanitizeProviderConfigValue(options.model, { GEMINI_API_KEY: key }, processEnv) ||
|
||||
sanitizeProviderConfigValue(
|
||||
processEnv.GEMINI_MODEL,
|
||||
{ GEMINI_API_KEY: key },
|
||||
processEnv,
|
||||
) ||
|
||||
sanitizeProviderConfigValue(options.model, secretSource) ||
|
||||
sanitizeProviderConfigValue(processEnv.GEMINI_MODEL, secretSource) ||
|
||||
DEFAULT_GEMINI_MODEL,
|
||||
}
|
||||
|
||||
@@ -178,12 +268,8 @@ export function buildGeminiProfileEnv(options: {
|
||||
}
|
||||
|
||||
const baseUrl =
|
||||
sanitizeProviderConfigValue(options.baseUrl, { GEMINI_API_KEY: key }, processEnv) ||
|
||||
sanitizeProviderConfigValue(
|
||||
processEnv.GEMINI_BASE_URL,
|
||||
{ GEMINI_API_KEY: key },
|
||||
processEnv,
|
||||
)
|
||||
sanitizeProviderConfigValue(options.baseUrl, secretSource) ||
|
||||
sanitizeProviderConfigValue(processEnv.GEMINI_BASE_URL, secretSource)
|
||||
if (baseUrl) {
|
||||
env.GEMINI_BASE_URL = baseUrl
|
||||
}
|
||||
@@ -205,15 +291,14 @@ export function buildOpenAIProfileEnv(options: {
|
||||
}
|
||||
|
||||
const defaultModel = getGoalDefaultOpenAIModel(options.goal)
|
||||
const secretSource: SecretValueSource = { OPENAI_API_KEY: key }
|
||||
const shellOpenAIModel = sanitizeProviderConfigValue(
|
||||
processEnv.OPENAI_MODEL,
|
||||
{ OPENAI_API_KEY: key },
|
||||
processEnv,
|
||||
secretSource,
|
||||
)
|
||||
const shellOpenAIBaseUrl = sanitizeProviderConfigValue(
|
||||
processEnv.OPENAI_BASE_URL,
|
||||
{ OPENAI_API_KEY: key },
|
||||
processEnv,
|
||||
secretSource,
|
||||
)
|
||||
const shellOpenAIRequest = resolveProviderRequest({
|
||||
model: shellOpenAIModel,
|
||||
@@ -224,19 +309,11 @@ export function buildOpenAIProfileEnv(options: {
|
||||
|
||||
return {
|
||||
OPENAI_BASE_URL:
|
||||
sanitizeProviderConfigValue(
|
||||
options.baseUrl,
|
||||
{ OPENAI_API_KEY: key },
|
||||
processEnv,
|
||||
) ||
|
||||
sanitizeProviderConfigValue(options.baseUrl, secretSource) ||
|
||||
(useShellOpenAIConfig ? shellOpenAIBaseUrl : undefined) ||
|
||||
DEFAULT_OPENAI_BASE_URL,
|
||||
OPENAI_MODEL:
|
||||
sanitizeProviderConfigValue(
|
||||
options.model,
|
||||
{ OPENAI_API_KEY: key },
|
||||
processEnv,
|
||||
) ||
|
||||
sanitizeProviderConfigValue(options.model, secretSource) ||
|
||||
(useShellOpenAIConfig ? shellOpenAIModel : undefined) ||
|
||||
defaultModel,
|
||||
OPENAI_API_KEY: key,
|
||||
@@ -293,21 +370,19 @@ export function buildMistralProfileEnv(options: {
|
||||
const env: ProfileEnv = {
|
||||
MISTRAL_API_KEY: key,
|
||||
MISTRAL_MODEL:
|
||||
sanitizeProviderConfigValue(options.model, { MISTRAL_API_KEY: key }, processEnv) ||
|
||||
sanitizeProviderConfigValue(options.model, { MISTRAL_API_KEY: key }) ||
|
||||
sanitizeProviderConfigValue(
|
||||
processEnv.MISTRAL_MODEL,
|
||||
{ MISTRAL_API_KEY: key },
|
||||
processEnv,
|
||||
) ||
|
||||
DEFAULT_MISTRAL_MODEL,
|
||||
}
|
||||
|
||||
const baseUrl =
|
||||
sanitizeProviderConfigValue(options.baseUrl, { MISTRAL_API_KEY: key }, processEnv) ||
|
||||
sanitizeProviderConfigValue(options.baseUrl, { MISTRAL_API_KEY: key }) ||
|
||||
sanitizeProviderConfigValue(
|
||||
processEnv.MISTRAL_BASE_URL,
|
||||
{ MISTRAL_API_KEY: key },
|
||||
processEnv,
|
||||
)
|
||||
if (baseUrl) {
|
||||
env.MISTRAL_BASE_URL = baseUrl
|
||||
@@ -465,11 +540,11 @@ export async function buildLaunchEnv(options: {
|
||||
)
|
||||
const shellOpenAIModel = sanitizeProviderConfigValue(
|
||||
processEnv.OPENAI_MODEL,
|
||||
processEnv,
|
||||
processEnv as SecretValueSource,
|
||||
)
|
||||
const shellOpenAIBaseUrl = sanitizeProviderConfigValue(
|
||||
processEnv.OPENAI_BASE_URL,
|
||||
processEnv,
|
||||
processEnv as SecretValueSource,
|
||||
)
|
||||
const persistedGeminiModel = sanitizeProviderConfigValue(
|
||||
persistedEnv.GEMINI_MODEL,
|
||||
@@ -481,11 +556,11 @@ export async function buildLaunchEnv(options: {
|
||||
)
|
||||
const shellGeminiModel = sanitizeProviderConfigValue(
|
||||
processEnv.GEMINI_MODEL,
|
||||
processEnv,
|
||||
processEnv as SecretValueSource,
|
||||
)
|
||||
const shellGeminiBaseUrl = sanitizeProviderConfigValue(
|
||||
processEnv.GEMINI_BASE_URL,
|
||||
processEnv,
|
||||
processEnv as SecretValueSource,
|
||||
)
|
||||
const shellGeminiAccessToken =
|
||||
processEnv.GEMINI_ACCESS_TOKEN?.trim() || undefined
|
||||
@@ -567,19 +642,15 @@ export async function buildLaunchEnv(options: {
|
||||
|
||||
const shellMistralModel = sanitizeProviderConfigValue(
|
||||
processEnv.MISTRAL_MODEL,
|
||||
processEnv,
|
||||
)
|
||||
const persistedMistralModel = sanitizeProviderConfigValue(
|
||||
persistedEnv.MISTRAL_MODEL,
|
||||
persistedEnv,
|
||||
)
|
||||
const shellMistralBaseUrl = sanitizeProviderConfigValue(
|
||||
processEnv.MISTRAL_BASE_URL,
|
||||
processEnv,
|
||||
)
|
||||
const persistedMistralBaseUrl = sanitizeProviderConfigValue(
|
||||
persistedEnv.MISTRAL_BASE_URL,
|
||||
persistedEnv,
|
||||
)
|
||||
|
||||
env.MISTRAL_MODEL =
|
||||
|
||||
@@ -20,6 +20,8 @@ export type ProviderPreset =
|
||||
| 'openrouter'
|
||||
| 'lmstudio'
|
||||
| 'custom'
|
||||
| 'nvidia-nim'
|
||||
| 'minimax'
|
||||
|
||||
export type ProviderProfileInput = {
|
||||
provider?: ProviderProfile['provider']
|
||||
@@ -229,6 +231,24 @@ export function getProviderPresetDefaults(
|
||||
apiKey: process.env.OPENAI_API_KEY ?? '',
|
||||
requiresApiKey: false,
|
||||
}
|
||||
case 'nvidia-nim':
|
||||
return {
|
||||
provider: 'openai',
|
||||
name: 'NVIDIA NIM',
|
||||
baseUrl: 'https://integrate.api.nvidia.com/v1',
|
||||
model: 'nvidia/llama-3.1-nemotron-70b-instruct',
|
||||
apiKey: process.env.NVIDIA_API_KEY ?? '',
|
||||
requiresApiKey: true,
|
||||
}
|
||||
case 'minimax':
|
||||
return {
|
||||
provider: 'openai',
|
||||
name: 'MiniMax',
|
||||
baseUrl: 'https://api.minimax.io/v1',
|
||||
model: 'MiniMax-M2.5',
|
||||
apiKey: process.env.MINIMAX_API_KEY ?? '',
|
||||
requiresApiKey: true,
|
||||
}
|
||||
case 'ollama':
|
||||
default:
|
||||
return {
|
||||
@@ -365,6 +385,11 @@ export function clearProviderProfileEnvFromProcessEnv(
|
||||
delete processEnv.ANTHROPIC_API_KEY
|
||||
delete processEnv[PROFILE_ENV_APPLIED_FLAG]
|
||||
delete processEnv[PROFILE_ENV_APPLIED_ID]
|
||||
|
||||
// Clear provider-specific API keys
|
||||
delete processEnv.MINIMAX_API_KEY
|
||||
delete processEnv.NVIDIA_API_KEY
|
||||
delete processEnv.NVIDIA_NIM
|
||||
}
|
||||
|
||||
export function applyProviderProfileToProcessEnv(profile: ProviderProfile): void {
|
||||
@@ -395,6 +420,14 @@ export function applyProviderProfileToProcessEnv(profile: ProviderProfile): void
|
||||
|
||||
if (profile.apiKey) {
|
||||
process.env.OPENAI_API_KEY = profile.apiKey
|
||||
// Also set provider-specific API keys for detection
|
||||
const baseUrl = profile.baseUrl.toLowerCase()
|
||||
if (baseUrl.includes('minimax')) {
|
||||
process.env.MINIMAX_API_KEY = profile.apiKey
|
||||
}
|
||||
if (baseUrl.includes('nvidia') || baseUrl.includes('integrate.api.nvidia')) {
|
||||
process.env.NVIDIA_API_KEY = profile.apiKey
|
||||
}
|
||||
} else {
|
||||
delete process.env.OPENAI_API_KEY
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user