diff --git a/apps/web/src/lib/ai-gateway/providers/direct-byok/direct-byok-definitions.ts b/apps/web/src/lib/ai-gateway/providers/direct-byok/direct-byok-definitions.ts index 2d7167942f..b4ffcdc478 100644 --- a/apps/web/src/lib/ai-gateway/providers/direct-byok/direct-byok-definitions.ts +++ b/apps/web/src/lib/ai-gateway/providers/direct-byok/direct-byok-definitions.ts @@ -8,6 +8,7 @@ import kimiCoding from './kimi-coding'; import martian from './martian'; import morph from './morph'; import neuralwatt from './neurowatt'; +import nvidiaByok from './nvidia-byok'; import ollamaCloud from './ollama-cloud'; import openCodeGo from './opencode-go'; import orcarouter from './orcarouter'; @@ -26,6 +27,7 @@ export default [ martian, morph, neuralwatt, + nvidiaByok, ollamaCloud, openCodeGo, orcarouter, diff --git a/apps/web/src/lib/ai-gateway/providers/direct-byok/direct-byok-meta.ts b/apps/web/src/lib/ai-gateway/providers/direct-byok/direct-byok-meta.ts index c0754bb667..5860086fc2 100644 --- a/apps/web/src/lib/ai-gateway/providers/direct-byok/direct-byok-meta.ts +++ b/apps/web/src/lib/ai-gateway/providers/direct-byok/direct-byok-meta.ts @@ -11,6 +11,7 @@ export const DIRECT_BYOK_PROVIDERS_META = { martian: 'Martian', 'morph-byok': 'Morph BYOK', neuralwatt: 'Neuralwatt', + 'nvidia-byok': 'NVIDIA BYOK', 'ollama-cloud': 'Ollama Cloud', 'opencode-go': 'OpenCode Go', orcarouter: 'OrcaRouter', diff --git a/apps/web/src/lib/ai-gateway/providers/direct-byok/nvidia-byok.ts b/apps/web/src/lib/ai-gateway/providers/direct-byok/nvidia-byok.ts new file mode 100644 index 0000000000..ad30bce1d7 --- /dev/null +++ b/apps/web/src/lib/ai-gateway/providers/direct-byok/nvidia-byok.ts @@ -0,0 +1,35 @@ +import { cachedEnhancedDirectByokModelList } from '@/lib/ai-gateway/providers/direct-byok/model-list'; +import type { DirectByokProvider } from '@/lib/ai-gateway/providers/direct-byok/types'; + +export default { + id: 'nvidia-byok', + base_url: 'https://integrate.api.nvidia.com/v1', + supported_chat_apis: ['chat_completions'], + default_ai_sdk_provider: 'openai-compatible', + transformRequest(context) { + const { request } = context; + if (request.kind !== 'chat_completions') { + return; + } + + request.body.reasoning_effort ??= request.body.reasoning?.effort ?? undefined; + + delete request.body.provider; + delete request.body.transforms; + delete request.body.reasoning; + delete request.body.safety_identifier; + delete request.body.prompt_cache_key; + }, + models: cachedEnhancedDirectByokModelList({ + providerId: 'nvidia-byok', + recommendedModels: [ + { + id: 'nvidia/nemotron-3-super-120b-a12b', + name: 'Nemotron 3 Super 120B A12B', + flags: ['reasoning'], + context_length: 262144, + max_completion_tokens: 262144, + }, + ], + }), +} satisfies DirectByokProvider; diff --git a/apps/web/src/lib/ai-gateway/providers/direct-byok/sync-direct-byok.test.ts b/apps/web/src/lib/ai-gateway/providers/direct-byok/sync-direct-byok.test.ts index dfa1b955fb..27a8063353 100644 --- a/apps/web/src/lib/ai-gateway/providers/direct-byok/sync-direct-byok.test.ts +++ b/apps/web/src/lib/ai-gateway/providers/direct-byok/sync-direct-byok.test.ts @@ -227,4 +227,60 @@ describe('parseModelsDevProviderModels', () => { expect(models.map(model => model.id)).toEqual(['available']); expect(models[0].context_length).toBe(128_000); }); + + test('excludes explicit capability mismatches while accepting missing metadata', () => { + const model = (id: string, overrides: Record = {}) => ({ + id, + name: id, + tool_call: true, + modalities: { input: ['text'], output: ['text'] }, + ...overrides, + }); + const models = parseModelsDevProviderModels( + { + models: { + chat: model('nvidia/chat', { + reasoning: true, + reasoning_options: [{ type: 'effort', values: ['none', 'high', 'max'] }], + limit: { context: 128000 }, + }), + vision: model('nvidia/vision', { + modalities: { input: ['text', 'image'], output: ['text'] }, + }), + unknownCapabilities: { id: 'nvidia/unknown' }, + unavailable: model('nvidia/unavailable'), + noTools: model('nvidia/no-tools', { tool_call: false }), + noTextInput: model('nvidia/no-text-input', { + modalities: { input: ['image'], output: ['text'] }, + }), + }, + }, + 'nvidia-byok', + new Set([ + 'nvidia/chat', + 'nvidia/vision', + 'nvidia/unknown', + 'nvidia/no-tools', + 'nvidia/no-text-input', + ]) + ); + + expect(models).toEqual([ + expect.objectContaining({ + id: 'nvidia/chat', + context_length: 128000, + flags: ['reasoning'], + variants: { + none: { reasoning: { enabled: false, effort: 'none' } }, + high: { reasoning: { enabled: true, effort: 'high' } }, + max: { reasoning: { enabled: true, effort: 'max' } }, + }, + }), + expect.objectContaining({ + id: 'nvidia/vision', + input_modalities: ['text', 'image'], + }), + expect.objectContaining({ id: 'nvidia/unknown' }), + ]); + }); }); diff --git a/apps/web/src/lib/ai-gateway/providers/direct-byok/sync-direct-byok.ts b/apps/web/src/lib/ai-gateway/providers/direct-byok/sync-direct-byok.ts index 9168743382..ce64f1f891 100644 --- a/apps/web/src/lib/ai-gateway/providers/direct-byok/sync-direct-byok.ts +++ b/apps/web/src/lib/ai-gateway/providers/direct-byok/sync-direct-byok.ts @@ -64,6 +64,7 @@ const ModelsDevModelSchema = z.object({ output: z.array(ModalitySchema).optional(), }) .optional(), + tool_call: z.boolean().optional(), }); const ModelsDevProviderSchema = z.object({ @@ -186,7 +187,9 @@ export function parseModelsDevProviderModels( model => model.status !== 'deprecated' && (!model.modalities?.output || model.modalities.output.includes('text')) && - (!availableModelIds || availableModelIds.has(model.id)) + (!availableModelIds || availableModelIds.has(model.id)) && + model.tool_call !== false && + (!model.modalities?.input || model.modalities.input.includes('text')) ) .map(model => { const modelId = `${providerId}/${model.id}`.toLowerCase(); @@ -254,6 +257,7 @@ const FETCHERS: ReadonlyArray = [ label: 'Neuralwatt', url: 'https://api.neuralwatt.com/v1/models', }), + modelsDevFetcher('nvidia-byok', 'nvidia', 'https://integrate.api.nvidia.com/v1/models'), openAICompatibleFetcher({ providerId: 'chutes-byok', label: 'Chutes', diff --git a/apps/web/src/lib/ai-gateway/providers/openrouter/inference-provider-id.ts b/apps/web/src/lib/ai-gateway/providers/openrouter/inference-provider-id.ts index 64b6f8a3c9..4952435c79 100644 --- a/apps/web/src/lib/ai-gateway/providers/openrouter/inference-provider-id.ts +++ b/apps/web/src/lib/ai-gateway/providers/openrouter/inference-provider-id.ts @@ -112,6 +112,7 @@ export const DirectUserByokInferenceProviderIdSchema = z.enum([ 'martian', 'morph-byok', 'neuralwatt', + 'nvidia-byok', 'ollama-cloud', 'opencode-go', 'orcarouter', @@ -156,6 +157,8 @@ export const UserByokTestModels = { [DirectUserByokInferenceProviderIdSchema.enum.martian]: 'google/gemini-3.5-flash', [DirectUserByokInferenceProviderIdSchema.enum['morph-byok']]: 'morph-qwen35-397b', [DirectUserByokInferenceProviderIdSchema.enum.neuralwatt]: 'Qwen/Qwen3.5-35B-A3B', + [DirectUserByokInferenceProviderIdSchema.enum['nvidia-byok']]: + 'nvidia/nemotron-3-super-120b-a12b', [DirectUserByokInferenceProviderIdSchema.enum['orcarouter']]: 'google/gemini-3.6-flash', [DirectUserByokInferenceProviderIdSchema.enum['crofai']]: 'deepseek-v4-flash', [DirectUserByokInferenceProviderIdSchema.enum['synthetic']]: 'hf:zai-org/GLM-5.1',