diff --git a/packages/i18n/src/locales/en/settings.yaml b/packages/i18n/src/locales/en/settings.yaml
index e9e416045..04ef1a459 100644
--- a/packages/i18n/src/locales/en/settings.yaml
+++ b/packages/i18n/src/locales/en/settings.yaml
@@ -743,6 +743,9 @@ pages:
openai-compatible:
description: OpenAI Compatible
title: OpenAI Compatible
+ nvidia:
+ description: build.nvidia.com
+ title: NVIDIA NIM
n1n:
description: n1n.ai - High-performance AI API provider.
title: n1n
diff --git a/packages/i18n/src/locales/es/settings.yaml b/packages/i18n/src/locales/es/settings.yaml
index 4fc58ac1f..254672fec 100644
--- a/packages/i18n/src/locales/es/settings.yaml
+++ b/packages/i18n/src/locales/es/settings.yaml
@@ -711,6 +711,9 @@ pages:
openai-compatible:
description: OpenAI Compatible
title: OpenAI Compatible
+ nvidia:
+ description: build.nvidia.com
+ title: NVIDIA NIM
n1n:
description: n1n.ai - High-performance AI API provider.
title: n1n
diff --git a/packages/i18n/src/locales/fr/settings.yaml b/packages/i18n/src/locales/fr/settings.yaml
index d32e200c2..4e67c1599 100644
--- a/packages/i18n/src/locales/fr/settings.yaml
+++ b/packages/i18n/src/locales/fr/settings.yaml
@@ -711,6 +711,9 @@ pages:
openai-compatible:
description: Compatible avec OpenAI
title: Compatible avec OpenAI
+ nvidia:
+ description: build.nvidia.com
+ title: NVIDIA NIM
n1n:
description: n1n.ai - Fournisseur d'API d'IA haute performance.
title: n1n
diff --git a/packages/i18n/src/locales/ja/settings.yaml b/packages/i18n/src/locales/ja/settings.yaml
index 0890cc32a..4f984fb8f 100644
--- a/packages/i18n/src/locales/ja/settings.yaml
+++ b/packages/i18n/src/locales/ja/settings.yaml
@@ -711,6 +711,9 @@ pages:
openai-compatible:
description: OpenAI互換
title: OpenAI互換
+ nvidia:
+ description: build.nvidia.com
+ title: NVIDIA NIM
n1n:
description: n1n.ai - 高性能なAI APIプロバイダー。
title: n1n
diff --git a/packages/i18n/src/locales/ko/settings.yaml b/packages/i18n/src/locales/ko/settings.yaml
index b28f3f638..267a54f7b 100644
--- a/packages/i18n/src/locales/ko/settings.yaml
+++ b/packages/i18n/src/locales/ko/settings.yaml
@@ -711,6 +711,9 @@ pages:
openai-compatible:
description: OpenAI 호환
title: OpenAI 호환
+ nvidia:
+ description: build.nvidia.com
+ title: NVIDIA NIM
n1n:
description: n1n.ai - 고성능 AI API 제공자.
title: n1n
diff --git a/packages/i18n/src/locales/ru/settings.yaml b/packages/i18n/src/locales/ru/settings.yaml
index 8bf1c4ff4..d95d71633 100644
--- a/packages/i18n/src/locales/ru/settings.yaml
+++ b/packages/i18n/src/locales/ru/settings.yaml
@@ -711,6 +711,9 @@ pages:
openai-compatible:
description: Совместимый с OpenAI
title: OpenAI Compatible
+ nvidia:
+ description: build.nvidia.com
+ title: NVIDIA NIM
n1n:
description: n1n.ai - High-performance AI API provider.
title: n1n
diff --git a/packages/i18n/src/locales/vi/settings.yaml b/packages/i18n/src/locales/vi/settings.yaml
index c1e7d96c3..313164f21 100644
--- a/packages/i18n/src/locales/vi/settings.yaml
+++ b/packages/i18n/src/locales/vi/settings.yaml
@@ -711,6 +711,9 @@ pages:
openai-compatible:
description: Tương thích OpenAI
title: OpenAI Compatible
+ nvidia:
+ description: build.nvidia.com
+ title: NVIDIA NIM
n1n:
description: n1n.ai - High-performance AI API provider.
title: n1n
diff --git a/packages/i18n/src/locales/zh-Hans/settings.yaml b/packages/i18n/src/locales/zh-Hans/settings.yaml
index 73daf66a3..590fb13a3 100644
--- a/packages/i18n/src/locales/zh-Hans/settings.yaml
+++ b/packages/i18n/src/locales/zh-Hans/settings.yaml
@@ -711,6 +711,9 @@ pages:
openai-compatible:
description: OpenAI 兼容 API
title: OpenAI 兼容 API
+ nvidia:
+ description: build.nvidia.com
+ title: NVIDIA NIM
n1n:
description: n1n.ai - 高性能的 AI API 提供商
title: n1n
diff --git a/packages/i18n/src/locales/zh-Hant/settings.yaml b/packages/i18n/src/locales/zh-Hant/settings.yaml
index 292310e06..54074b6b5 100644
--- a/packages/i18n/src/locales/zh-Hant/settings.yaml
+++ b/packages/i18n/src/locales/zh-Hant/settings.yaml
@@ -711,6 +711,9 @@ pages:
openai-compatible:
description: OpenAI 相容
title: OpenAI Compatible
+ nvidia:
+ description: build.nvidia.com
+ title: NVIDIA NIM
n1n:
description: n1n.ai - High-performance AI API provider.
title: n1n
diff --git a/packages/stage-ui/src/components/scenarios/dialogs/onboarding/onboarding.vue b/packages/stage-ui/src/components/scenarios/dialogs/onboarding/onboarding.vue
index d9151cf2f..8106d7780 100644
--- a/packages/stage-ui/src/components/scenarios/dialogs/onboarding/onboarding.vue
+++ b/packages/stage-ui/src/components/scenarios/dialogs/onboarding/onboarding.vue
@@ -30,7 +30,7 @@ const {
// Popular providers for first-time setup
const popularProviders = computed(() => {
- const popular = ['openai', 'anthropic', 'google-generative-ai', 'groq', 'openrouter-ai', 'ollama', 'deepseek', 'player2', 'openai-compatible']
+ const popular = ['openai', 'anthropic', 'google-generative-ai', 'groq', 'nvidia', 'openrouter-ai', 'ollama', 'deepseek', 'player2', 'openai-compatible']
return allChatProvidersMetadata.value
.filter(provider => popular.includes(provider.id))
.sort((a, b) => popular.indexOf(a.id) - popular.indexOf(b.id))
diff --git a/packages/stage-ui/src/components/scenarios/dialogs/onboarding/step-model-selection.vue b/packages/stage-ui/src/components/scenarios/dialogs/onboarding/step-model-selection.vue
index de4de2c22..e437a1ae5 100644
--- a/packages/stage-ui/src/components/scenarios/dialogs/onboarding/step-model-selection.vue
+++ b/packages/stage-ui/src/components/scenarios/dialogs/onboarding/step-model-selection.vue
@@ -35,9 +35,22 @@ const {
-
+
+
+
+ {{ t('settings.dialogs.onboarding.no-models') }}
+
+
+
+ {{ t('settings.dialogs.onboarding.no-models-help') }}
+
+
+
+
-
-
-
- {{ t('settings.dialogs.onboarding.no-models') }}
-
-
-
- {{ t('settings.dialogs.onboarding.no-models-help') }}
-
-
-
diff --git a/packages/stage-ui/src/components/scenarios/dialogs/onboarding/step-provider-configuration.vue b/packages/stage-ui/src/components/scenarios/dialogs/onboarding/step-provider-configuration.vue
index b3d436560..beeaf4e61 100644
--- a/packages/stage-ui/src/components/scenarios/dialogs/onboarding/step-provider-configuration.vue
+++ b/packages/stage-ui/src/components/scenarios/dialogs/onboarding/step-provider-configuration.vue
@@ -146,6 +146,7 @@ function getApiKeyPlaceholder(providerId: string): string {
'modelscope': 'ms-...',
'fireworks-ai': 'fw-...',
'featherless-ai': 'fw-...',
+ 'nvidia': 'nvapi-...',
'novita-ai': 'nvt-...',
}
diff --git a/packages/stage-ui/src/libs/providers/providers/index.ts b/packages/stage-ui/src/libs/providers/providers/index.ts
index 5c169cc95..6f6f8bb2d 100644
--- a/packages/stage-ui/src/libs/providers/providers/index.ts
+++ b/packages/stage-ui/src/libs/providers/providers/index.ts
@@ -4,6 +4,7 @@ import './lm-studio'
import './openai-compatible'
import './n1n'
import './openrouter-ai'
+import './nvidia'
import './groq'
import './anthropic'
import './google-generative-ai'
diff --git a/packages/stage-ui/src/libs/providers/providers/nvidia/index.ts b/packages/stage-ui/src/libs/providers/providers/nvidia/index.ts
new file mode 100644
index 000000000..e53ad5afd
--- /dev/null
+++ b/packages/stage-ui/src/libs/providers/providers/nvidia/index.ts
@@ -0,0 +1,54 @@
+import { isStageTamagotchi } from '@proj-airi/stage-shared'
+import { createOpenAI } from '@xsai-ext/providers/create'
+import { z } from 'zod'
+
+import { createOpenAICompatibleValidators } from '../../validators/openai-compatible'
+import { defineProvider } from '../registry'
+
+const nvidiaConfigSchema = z.object({
+ apiKey: z
+ .string('API Key'),
+ baseUrl: z
+ .string('Base URL')
+ .optional()
+ .default('https://integrate.api.nvidia.com/v1/'),
+})
+
+type NvidiaConfig = z.input
+
+export const providerNvidia = defineProvider({
+ id: 'nvidia',
+ name: 'NVIDIA NIM',
+ nameLocalize: ({ t }) => t('settings.pages.providers.provider.nvidia.title'),
+ description: 'build.nvidia.com',
+ descriptionLocalize: ({ t }) => t('settings.pages.providers.provider.nvidia.description'),
+ tasks: ['chat'],
+ icon: 'i-simple-icons:nvidia',
+ isAvailableBy: isStageTamagotchi,
+
+ createProviderConfig: ({ t }) => nvidiaConfigSchema.extend({
+ apiKey: nvidiaConfigSchema.shape.apiKey.meta({
+ labelLocalized: t('settings.pages.providers.catalog.edit.config.common.fields.field.api-key.label'),
+ descriptionLocalized: t('settings.pages.providers.catalog.edit.config.common.fields.field.api-key.description'),
+ placeholderLocalized: t('settings.pages.providers.catalog.edit.config.common.fields.field.api-key.placeholder'),
+ type: 'password',
+ }),
+ baseUrl: nvidiaConfigSchema.shape.baseUrl.meta({
+ labelLocalized: t('settings.pages.providers.catalog.edit.config.common.fields.field.base-url.label'),
+ descriptionLocalized: t('settings.pages.providers.catalog.edit.config.common.fields.field.base-url.description'),
+ placeholderLocalized: t('settings.pages.providers.catalog.edit.config.common.fields.field.base-url.placeholder'),
+ }),
+ }),
+ createProvider(config) {
+ return createOpenAI(config.apiKey, config.baseUrl)
+ },
+
+ validationRequiredWhen(config) {
+ return !!config.apiKey?.trim()
+ },
+ validators: {
+ ...createOpenAICompatibleValidators({
+ checks: ['connectivity', 'model_list'],
+ }),
+ },
+})
diff --git a/packages/stage-ui/src/libs/providers/validators/openai-compatible.test.ts b/packages/stage-ui/src/libs/providers/validators/openai-compatible.test.ts
new file mode 100644
index 000000000..dde3cb3e7
--- /dev/null
+++ b/packages/stage-ui/src/libs/providers/validators/openai-compatible.test.ts
@@ -0,0 +1,77 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+
+import { createOpenAICompatibleValidators } from './openai-compatible'
+
+const {
+ generateTextMock,
+ listModelsMock,
+} = vi.hoisted(() => ({
+ generateTextMock: vi.fn(),
+ listModelsMock: vi.fn(),
+}))
+
+vi.mock('@xsai/generate-text', () => ({
+ generateText: generateTextMock,
+}))
+
+vi.mock('@xsai/model', () => ({
+ listModels: listModelsMock,
+}))
+
+function getProviderValidators(options?: Parameters[0]) {
+ const validators = createOpenAICompatibleValidators(options)
+
+ return (validators?.validateProvider || []).map(create => create({
+ t: (input: string) => input,
+ } as any))
+}
+
+describe('createOpenAICompatibleValidators', () => {
+ const config = {
+ apiKey: 'test-key',
+ baseUrl: 'https://example.com/v1/',
+ }
+ const provider = {
+ model: () => ({
+ apiKey: config.apiKey,
+ baseURL: config.baseUrl,
+ }),
+ }
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ it('does not probe chat completions with a synthetic fallback model', async () => {
+ listModelsMock.mockResolvedValue([])
+
+ const [connectivityValidator, chatValidator] = getProviderValidators({
+ checks: ['connectivity', 'chat_completions'],
+ })
+
+ const connectivityResult = await connectivityValidator.validator(config, provider as any, undefined as any, undefined as any)
+ const chatResult = await chatValidator.validator(config, provider as any, undefined as any, undefined as any)
+
+ expect(connectivityResult.valid).toBe(false)
+ expect(connectivityResult.reason).toContain('No model available for validation.')
+ expect(chatResult.valid).toBe(false)
+ expect(chatResult.reason).toContain('No model available for validation.')
+ expect(generateTextMock).not.toHaveBeenCalled()
+ })
+
+ it('allows providers to skip chat probing when they do not expose model listing', async () => {
+ listModelsMock.mockResolvedValue([])
+
+ const [connectivityValidator, chatValidator] = getProviderValidators({
+ checks: ['connectivity', 'chat_completions'],
+ allowValidationWithoutModel: true,
+ })
+
+ const connectivityResult = await connectivityValidator.validator(config, provider as any, undefined as any, undefined as any)
+ const chatResult = await chatValidator.validator(config, provider as any, undefined as any, undefined as any)
+
+ expect(connectivityResult.valid).toBe(true)
+ expect(chatResult.valid).toBe(true)
+ expect(generateTextMock).not.toHaveBeenCalled()
+ })
+})
diff --git a/packages/stage-ui/src/libs/providers/validators/openai-compatible.ts b/packages/stage-ui/src/libs/providers/validators/openai-compatible.ts
index 8079f3ba0..71bce86d2 100644
--- a/packages/stage-ui/src/libs/providers/validators/openai-compatible.ts
+++ b/packages/stage-ui/src/libs/providers/validators/openai-compatible.ts
@@ -15,6 +15,7 @@ type OpenAICompatibleValidationCheck = 'connectivity' | 'model_list' | 'chat_com
interface OpenAICompatibleValidationOptions {
checks?: OpenAICompatibleValidationCheck[]
additionalHeaders?: Record
+ allowValidationWithoutModel?: boolean
schedule?: {
mode: 'once' | 'interval'
intervalMs?: number
@@ -88,16 +89,14 @@ async function pickValidationModel | undefined,
-): Promise {
- const fallback = 'test'
-
+): Promise {
try {
const models = await resolveModels(config, provider, providerExtra)
const modelId = extractModelId(models.find(model => !shouldSkipModelId(extractModelId(model))))
- return modelId || fallback
+ return modelId || null
}
catch {
- return fallback
+ return null
}
}
@@ -106,6 +105,7 @@ export function createOpenAICompatibleValidators['validators'] {
const checks = options?.checks ?? ['connectivity', 'model_list', 'chat_completions']
const additionalHeaders = options?.additionalHeaders
+ const missingValidationModelReason = 'No model available for validation. Configure a model manually and try again.'
interface ChatCheckResult {
connectivityOk: boolean
@@ -114,6 +114,48 @@ export function createOpenAICompatibleValidators | undefined,
+ ): Promise {
+ const model = await pickValidationModel(config, provider, providerExtra)
+
+ if (!model) {
+ if (options?.allowValidationWithoutModel) {
+ return { connectivityOk: true, chatOk: true }
+ }
+
+ return {
+ connectivityOk: false,
+ chatOk: false,
+ errorMessage: missingValidationModelReason,
+ }
+ }
+
+ try {
+ await generateText({
+ apiKey: config.apiKey,
+ baseURL: config.baseUrl!,
+ headers: additionalHeaders,
+ model,
+ messages: message.messages(message.user('ping')),
+ max_tokens: 1,
+ })
+
+ return { connectivityOk: true, chatOk: true }
+ }
+ catch (e) {
+ if (isNetworkError(e)) {
+ return { connectivityOk: false, chatOk: false, error: e, errorMessage: errorMessageFrom(e) }
+ }
+
+ const status = extractStatusCode(e)
+ const chatOk = status === 400 || Boolean(status && status >= 200 && status < 300)
+ return { connectivityOk: true, chatOk, errorMessage: errorMessageFrom(e) }
+ }
+ }
+
const chatCheckCacheKey = 'openai-compatible:chat-check'
const chatCheckMutexKey = 'openai-compatible:chat-check:mutex'
const getChatCheckResult = async (
@@ -128,28 +170,7 @@ export function createOpenAICompatibleValidators= 200 && status < 300)
- return { connectivityOk: true, chatOk, errorMessage: errorMessageFrom(e) }
- }
+ return runChatCheck(config, provider, providerExtra)
}
let mutex = cache.get(chatCheckMutexKey) as Mutex | undefined
@@ -165,30 +186,7 @@ export function createOpenAICompatibleValidators {
- const model = await pickValidationModel(config, provider, providerExtra)
- try {
- await generateText({
- apiKey: config.apiKey,
- baseURL: config.baseUrl!,
- headers: additionalHeaders,
- model,
- messages: message.messages(message.user('ping')),
- max_tokens: 1,
- })
-
- return { connectivityOk: true, chatOk: true }
- }
- catch (e) {
- if (isNetworkError(e)) {
- return { connectivityOk: false, chatOk: false, error: e, errorMessage: errorMessageFrom(e) }
- }
-
- const status = extractStatusCode(e)
- const chatOk = status === 400 || Boolean(status && status >= 200 && status < 300)
- return { connectivityOk: true, chatOk, errorMessage: errorMessageFrom(e) }
- }
- })()
+ const sharedCheck = runChatCheck(config, provider, providerExtra)
cache.set(chatCheckCacheKey, sharedCheck)