diff --git a/open-sse/providers/capabilities.js b/open-sse/providers/capabilities.js index 98f4ed47..c73a4bc8 100644 --- a/open-sse/providers/capabilities.js +++ b/open-sse/providers/capabilities.js @@ -102,6 +102,15 @@ export const MODEL_CAPABILITIES = { * Provider-specific capability overrides. Keyed by provider alias/id. */ export const PROVIDER_CAPABILITIES = { + // NVIDIA NIM is OpenAI-compatible → rejects MiniMax/GLM native `thinking` field. + // Force openai reasoning_effort format for its reasoning models. #issue + "nvidia": { + "minimaxai/minimax-m2.7": { reasoning: true, thinkingFormat: "openai", thinkingCanDisable: false, contextWindow: 200000, maxOutput: 131072 }, + "minimaxai/minimax-m3": { vision: true, reasoning: true, thinkingFormat: "openai", thinkingCanDisable: false, contextWindow: 512000, maxOutput: 131072 }, + "z-ai/glm-5.2": { reasoning: true, thinkingFormat: "openai", contextWindow: 200000, maxOutput: 128000 }, + "deepseek-ai/deepseek-v4-pro": { reasoning: true, thinkingFormat: "openai", contextWindow: 1000000, maxOutput: 65536 }, + "deepseek-ai/deepseek-v4-flash": { reasoning: true, thinkingFormat: "openai", contextWindow: 1000000, maxOutput: 65536 }, + }, // CodeBuddy.cn — authoritative per-model metadata from the gateway's model // config (contextWindow=maxInputTokens, maxOutput=maxOutputTokens, vision= // supportsImages). Every model reasons via OpenAI-style reasoning_effort diff --git a/open-sse/providers/registry/nvidia.js b/open-sse/providers/registry/nvidia.js index 35a1f768..9522611a 100644 --- a/open-sse/providers/registry/nvidia.js +++ b/open-sse/providers/registry/nvidia.js @@ -20,8 +20,13 @@ export default { validateUrl: "https://integrate.api.nvidia.com/v1/models", }, models: [ - { id: "minimaxai/minimax-m2.7", name: "Minimax M2.7" }, - { id: "z-ai/glm4.7", name: "GLM 4.7" }, + { id: "minimaxai/minimax-m2.7", name: "MiniMax M2.7" }, + { id: "minimaxai/minimax-m3", name: "MiniMax M3" }, + { id: "z-ai/glm-5.2", name: "GLM 5.2" }, + { id: "deepseek-ai/deepseek-v4-pro", name: "DeepSeek V4 Pro" }, + { id: "deepseek-ai/deepseek-v4-flash", name: "DeepSeek V4 Flash" }, + { id: "moonshotai/kimi-k2.6", name: "Kimi K2.6" }, + { id: "nvidia/nemotron-3-ultra-550b-a55b", name: "Nemotron 3 Ultra" }, { id: "nvidia/nv-embedqa-e5-v5", name: "NV EmbedQA E5 v5", kind: "embedding" }, { id: "nvidia/parakeet-ctc-1.1b-asr", name: "Parakeet CTC 1.1B", params: ["language"], kind: "stt" }, { id: "fastpitch", name: "FastPitch", kind: "tts" }, diff --git a/public/providers/clinepass.png b/public/providers/clinepass.png new file mode 100644 index 00000000..be241899 Binary files /dev/null and b/public/providers/clinepass.png differ diff --git a/tests/translator/real/nvidia-thinking.e2e.test.js b/tests/translator/real/nvidia-thinking.e2e.test.js new file mode 100644 index 00000000..2e2f67e9 --- /dev/null +++ b/tests/translator/real/nvidia-thinking.e2e.test.js @@ -0,0 +1,58 @@ +// E2E: hit live local proxy → verify nvidia MiniMax M2.7 doesn't 400 on +// unsupported "thinking" param (nvidia NIM is OpenAI-compatible). +// Requires dev server running on NV_E2E_PORT + an active router API key in DB. +// RUN_E2E=1 npx vitest run --config tests/vitest.config.js tests/translator/real/nvidia-thinking.e2e.test.js +import { describe, it, expect, beforeAll } from "vitest"; +import { getApiKeys } from "../../../src/lib/db/repos/apiKeysRepo.js"; + +const PORT = process.env.NV_E2E_PORT || "20127"; +const BASE = `http://localhost:${PORT}`; +const MODELS = [ + "nvidia/minimaxai/minimax-m2.7", + "nvidia/minimaxai/minimax-m3", + "nvidia/z-ai/glm-5.2", + "nvidia/deepseek-ai/deepseek-v4-pro", + "nvidia/deepseek-ai/deepseek-v4-flash", + "nvidia/moonshotai/kimi-k2.6", + "nvidia/nvidia/nemotron-3-ultra-550b-a55b", +]; +const RUN = process.env.RUN_E2E === "1"; +const maybe = RUN ? describe : describe.skip; + +async function drain(res) { + const reader = res.body.getReader(); + const decoder = new TextDecoder(); + let out = ""; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + out += decoder.decode(value, { stream: true }); + } + return out; +} + +maybe("nvidia thinking e2e", () => { + let apiKey = ""; + beforeAll(async () => { + const keys = await getApiKeys(); + apiKey = keys.find((k) => k.isActive)?.key || process.env.NV_E2E_KEY || ""; + }); + + it.each(MODELS)("%s with reasoning_effort -> no 'thinking' 400", async (model) => { + if (!apiKey) return expect(true).toBe(true); + const res = await fetch(`${BASE}/v1/chat/completions`, { + method: "POST", + headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` }, + body: JSON.stringify({ + model, + stream: true, + max_tokens: 64, + reasoning_effort: "low", + messages: [{ role: "user", content: "Reply with the single word: hi" }], + }), + }); + const raw = await drain(res); + expect(/Unsupported parameter.*thinking/i.test(raw), `${model} rejected 'thinking'`).toBe(false); + expect(res.status, `${model} bad status ${res.status}`).toBeLessThan(400); + }, 90000); +});