Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
7
extensions/nvidia/api.ts
Normal file
7
extensions/nvidia/api.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// Nvidia API module exposes the plugin public contract.
|
||||
export { buildNvidiaProvider, NVIDIA_DEFAULT_MODEL_ID } from "./provider-catalog.js";
|
||||
export {
|
||||
applyNvidiaConfig,
|
||||
applyNvidiaProviderConfig,
|
||||
NVIDIA_DEFAULT_MODEL_REF,
|
||||
} from "./onboard.js";
|
||||
323
extensions/nvidia/index.test.ts
Normal file
323
extensions/nvidia/index.test.ts
Normal file
@@ -0,0 +1,323 @@
|
||||
// Nvidia tests cover index plugin behavior.
|
||||
import fs from "node:fs";
|
||||
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
|
||||
import {
|
||||
registerSingleProviderPlugin,
|
||||
resolveProviderPluginChoice,
|
||||
} from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import plugin from "./index.js";
|
||||
import {
|
||||
clearNvidiaFeaturedModelCacheForTests,
|
||||
NVIDIA_FEATURED_MODELS_URL,
|
||||
} from "./provider-catalog.js";
|
||||
|
||||
const ssrfRuntimeMocks = vi.hoisted(() => ({
|
||||
fetchWithSsrFGuard: vi.fn(),
|
||||
ssrfPolicyFromHttpBaseUrlAllowedHostname: vi.fn((baseUrl: string) => ({
|
||||
allowedHostnames: [new URL(baseUrl).hostname],
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ssrfRuntimeMocks);
|
||||
|
||||
type NvidiaManifest = {
|
||||
providerAuthChoices?: Array<Record<string, unknown>>;
|
||||
};
|
||||
type RegisteredModelCatalogProvider = Parameters<
|
||||
ReturnType<typeof createTestPluginApi>["registerModelCatalogProvider"]
|
||||
>[0];
|
||||
|
||||
function readManifest(): NvidiaManifest {
|
||||
return JSON.parse(
|
||||
fs.readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8"),
|
||||
) as NvidiaManifest;
|
||||
}
|
||||
|
||||
async function registerNvidiaProvider() {
|
||||
return registerSingleProviderPlugin(plugin);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
clearNvidiaFeaturedModelCacheForTests();
|
||||
ssrfRuntimeMocks.fetchWithSsrFGuard.mockReset();
|
||||
ssrfRuntimeMocks.ssrfPolicyFromHttpBaseUrlAllowedHostname.mockClear();
|
||||
});
|
||||
|
||||
function mockFeaturedCatalogResponse(payload: unknown, status = 200) {
|
||||
ssrfRuntimeMocks.fetchWithSsrFGuard.mockResolvedValueOnce({
|
||||
response: Response.json(payload, { status }),
|
||||
finalUrl: NVIDIA_FEATURED_MODELS_URL,
|
||||
release: vi.fn(),
|
||||
});
|
||||
}
|
||||
|
||||
function registerNvidiaPluginApi() {
|
||||
const registeredProviders: string[] = [];
|
||||
const registeredModelCatalogProviders: RegisteredModelCatalogProvider[] = [];
|
||||
|
||||
plugin.register(
|
||||
createTestPluginApi({
|
||||
registerProvider(provider: { id: string }) {
|
||||
registeredProviders.push(provider.id);
|
||||
},
|
||||
registerModelCatalogProvider(provider) {
|
||||
registeredModelCatalogProviders.push(provider);
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return { registeredProviders, registeredModelCatalogProviders };
|
||||
}
|
||||
|
||||
function buildCatalogContext(apiKey?: string) {
|
||||
return {
|
||||
config: {},
|
||||
env: process.env,
|
||||
resolveProviderApiKey: () => ({ apiKey }),
|
||||
resolveProviderAuth: () => ({
|
||||
apiKey,
|
||||
mode: apiKey ? ("api_key" as const) : ("none" as const),
|
||||
source: apiKey ? ("env" as const) : ("none" as const),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function buildAugmentCatalogContext(apiKey?: string) {
|
||||
const env = { ...process.env };
|
||||
if (!apiKey) {
|
||||
delete env.NVIDIA_API_KEY;
|
||||
}
|
||||
return {
|
||||
...buildCatalogContext(apiKey),
|
||||
env,
|
||||
entries: [],
|
||||
};
|
||||
}
|
||||
|
||||
describe("nvidia provider hooks", () => {
|
||||
it("registers the nvidia provider with correct metadata", async () => {
|
||||
const provider = await registerNvidiaProvider();
|
||||
|
||||
expect(provider.id).toBe("nvidia");
|
||||
expect(provider.label).toBe("NVIDIA");
|
||||
expect(provider.docsPath).toBe("/providers/nvidia");
|
||||
expect(provider.envVars).toEqual(["NVIDIA_API_KEY"]);
|
||||
});
|
||||
|
||||
it("registers API-key auth choice metadata", async () => {
|
||||
const provider = await registerNvidiaProvider();
|
||||
|
||||
expect(provider.auth?.map((method) => method.id)).toEqual(["api-key"]);
|
||||
|
||||
const choice = resolveProviderPluginChoice({
|
||||
providers: [provider],
|
||||
choice: "nvidia-api-key",
|
||||
});
|
||||
expect(choice?.provider.id).toBe("nvidia");
|
||||
expect(choice?.method.id).toBe("api-key");
|
||||
expect(readManifest().providerAuthChoices).toStrictEqual([
|
||||
{
|
||||
provider: "nvidia",
|
||||
method: "api-key",
|
||||
choiceId: "nvidia-api-key",
|
||||
choiceLabel: "NVIDIA API key",
|
||||
groupId: "nvidia",
|
||||
groupLabel: "NVIDIA",
|
||||
groupHint: "Direct API key",
|
||||
optionKey: "nvidiaApiKey",
|
||||
cliFlag: "--nvidia-api-key",
|
||||
cliOption: "--nvidia-api-key <key>",
|
||||
cliDescription: "NVIDIA API key",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps nvidia auth setup metadata aligned", async () => {
|
||||
const provider = await registerNvidiaProvider();
|
||||
|
||||
expect(
|
||||
provider.auth.map((method) => ({
|
||||
id: method.id,
|
||||
label: method.label,
|
||||
hint: method.hint,
|
||||
choiceId: method.wizard?.choiceId,
|
||||
groupId: method.wizard?.groupId,
|
||||
groupLabel: method.wizard?.groupLabel,
|
||||
groupHint: method.wizard?.groupHint,
|
||||
})),
|
||||
).toEqual([
|
||||
{
|
||||
id: "api-key",
|
||||
label: "NVIDIA API key",
|
||||
hint: "Direct API key",
|
||||
choiceId: "nvidia-api-key",
|
||||
groupId: "nvidia",
|
||||
groupLabel: "NVIDIA",
|
||||
groupHint: "Direct API key",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps nvidia wizard setup metadata aligned", async () => {
|
||||
const provider = await registerNvidiaProvider();
|
||||
|
||||
expect(provider.wizard?.setup).toStrictEqual({
|
||||
choiceId: "nvidia-api-key",
|
||||
choiceLabel: "NVIDIA API key",
|
||||
groupId: "nvidia",
|
||||
groupLabel: "NVIDIA",
|
||||
groupHint: "Direct API key",
|
||||
methodId: "api-key",
|
||||
modelSelection: {
|
||||
promptWhenAuthChoiceProvided: true,
|
||||
allowKeepCurrent: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps nvidia model picker metadata aligned", async () => {
|
||||
const provider = await registerNvidiaProvider();
|
||||
|
||||
expect(provider.wizard?.modelPicker).toStrictEqual({
|
||||
label: "NVIDIA (custom)",
|
||||
hint: "Use NVIDIA-hosted open models",
|
||||
methodId: "api-key",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not override replay policy for standard openai-compatible transport", async () => {
|
||||
const provider = await registerNvidiaProvider();
|
||||
|
||||
// NVIDIA uses standard OpenAI-compatible API without custom replay logic
|
||||
expect(provider.buildReplayPolicy).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not override stream wrapper for standard models", async () => {
|
||||
const provider = await registerNvidiaProvider();
|
||||
|
||||
// NVIDIA uses standard streaming without custom wrappers
|
||||
expect(provider.wrapStreamFn).toBeUndefined();
|
||||
});
|
||||
|
||||
it("surfaces the bundled NVIDIA models without fetching when no NVIDIA API token is available", async () => {
|
||||
const provider = await registerNvidiaProvider();
|
||||
|
||||
const entries = await provider.augmentModelCatalog?.(buildAugmentCatalogContext());
|
||||
|
||||
expect(entries?.map((entry) => entry.id)).toEqual([
|
||||
"nvidia/nemotron-3-ultra-550b-a55b",
|
||||
"nvidia/nemotron-3-super-120b-a12b",
|
||||
"moonshotai/kimi-k2.5",
|
||||
"minimaxai/minimax-m2.7",
|
||||
"z-ai/glm-5.1",
|
||||
"minimaxai/minimax-m2.5",
|
||||
"z-ai/glm5",
|
||||
]);
|
||||
expect(entries?.every((entry) => entry.provider === "nvidia")).toBe(true);
|
||||
expect(ssrfRuntimeMocks.fetchWithSsrFGuard).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("surfaces the bundled NVIDIA models when authenticated featured catalog fetch fails", async () => {
|
||||
mockFeaturedCatalogResponse({ error: "unavailable" }, 503);
|
||||
const provider = await registerNvidiaProvider();
|
||||
|
||||
const entries = await provider.augmentModelCatalog?.(buildAugmentCatalogContext("nvapi-test"));
|
||||
|
||||
expect(entries?.map((entry) => entry.id)).toEqual([
|
||||
"nvidia/nemotron-3-ultra-550b-a55b",
|
||||
"nvidia/nemotron-3-super-120b-a12b",
|
||||
"moonshotai/kimi-k2.5",
|
||||
"minimaxai/minimax-m2.7",
|
||||
"z-ai/glm-5.1",
|
||||
"minimaxai/minimax-m2.5",
|
||||
"z-ai/glm5",
|
||||
]);
|
||||
expect(entries?.every((entry) => entry.provider === "nvidia")).toBe(true);
|
||||
expect(ssrfRuntimeMocks.fetchWithSsrFGuard).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("surfaces live featured NVIDIA models via augmentModelCatalog", async () => {
|
||||
mockFeaturedCatalogResponse({
|
||||
"featured-models": [
|
||||
{
|
||||
model: "minimaxai/minimax-m2.7",
|
||||
"model-name": "Minimax M2.7",
|
||||
context: 196608,
|
||||
"max-output": 8192,
|
||||
},
|
||||
],
|
||||
});
|
||||
const provider = await registerNvidiaProvider();
|
||||
|
||||
const entries = await provider.augmentModelCatalog?.(buildAugmentCatalogContext("nvapi-test"));
|
||||
|
||||
expect(entries?.map((entry) => entry.id)).toEqual(["minimaxai/minimax-m2.7"]);
|
||||
});
|
||||
|
||||
it("opts into literal provider-prefix preservation", async () => {
|
||||
const provider = await registerNvidiaProvider();
|
||||
|
||||
// NVIDIA's ids like nvidia/nemotron-... sit alongside moonshotai/...,
|
||||
// minimaxai/..., z-ai/... in the same catalog, so the leading nvidia/
|
||||
// is a vendor namespace rather than a redundant provider prefix. The
|
||||
// flag keeps the canonical ref as nvidia/nvidia/nemotron-... instead
|
||||
// of letting the default string-based dedupe collapse it.
|
||||
expect(provider.preserveLiteralProviderPrefix).toBe(true);
|
||||
});
|
||||
|
||||
it("registers nvidia provider through the plugin api", () => {
|
||||
const { registeredProviders, registeredModelCatalogProviders } = registerNvidiaPluginApi();
|
||||
|
||||
expect(registeredProviders).toStrictEqual(["nvidia"]);
|
||||
expect(registeredModelCatalogProviders.map((provider) => provider.provider)).toStrictEqual([
|
||||
"nvidia",
|
||||
]);
|
||||
});
|
||||
|
||||
it("registers static and live nvidia model catalog rows", async () => {
|
||||
mockFeaturedCatalogResponse({
|
||||
"featured-models": [
|
||||
{
|
||||
model: "minimaxai/minimax-m2.7",
|
||||
"model-name": "Minimax M2.7",
|
||||
context: 196608,
|
||||
"max-output": 8192,
|
||||
},
|
||||
],
|
||||
});
|
||||
const { registeredModelCatalogProviders } = registerNvidiaPluginApi();
|
||||
const catalogProvider = registeredModelCatalogProviders[0];
|
||||
|
||||
expect(catalogProvider?.provider).toBe("nvidia");
|
||||
expect(catalogProvider?.kinds).toStrictEqual(["text"]);
|
||||
|
||||
const staticRows = await catalogProvider?.staticCatalog?.(buildCatalogContext());
|
||||
expect(staticRows?.map((entry) => `${entry.source}:${entry.provider}/${entry.model}`)).toEqual([
|
||||
"static:nvidia/nvidia/nemotron-3-ultra-550b-a55b",
|
||||
"static:nvidia/nvidia/nemotron-3-super-120b-a12b",
|
||||
"static:nvidia/moonshotai/kimi-k2.5",
|
||||
"static:nvidia/minimaxai/minimax-m2.7",
|
||||
"static:nvidia/z-ai/glm-5.1",
|
||||
"static:nvidia/minimaxai/minimax-m2.5",
|
||||
"static:nvidia/z-ai/glm5",
|
||||
]);
|
||||
|
||||
await expect(catalogProvider?.liveCatalog?.(buildCatalogContext())).resolves.toEqual([]);
|
||||
|
||||
const liveRows = await catalogProvider?.liveCatalog?.(buildCatalogContext("nvapi-test"));
|
||||
expect(liveRows?.map((entry) => `${entry.source}:${entry.provider}/${entry.model}`)).toEqual([
|
||||
"live:nvidia/minimaxai/minimax-m2.7",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps static rows out of the live catalog when the featured catalog is unavailable", async () => {
|
||||
mockFeaturedCatalogResponse({ error: "unavailable" }, 503);
|
||||
const { registeredModelCatalogProviders } = registerNvidiaPluginApi();
|
||||
const catalogProvider = registeredModelCatalogProviders[0];
|
||||
|
||||
await expect(
|
||||
catalogProvider?.liveCatalog?.(buildCatalogContext("nvapi-test")),
|
||||
).resolves.toEqual([]);
|
||||
});
|
||||
});
|
||||
83
extensions/nvidia/index.ts
Normal file
83
extensions/nvidia/index.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
// Nvidia plugin entrypoint registers its OpenClaw integration.
|
||||
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
|
||||
import { applyNvidiaConfig, NVIDIA_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
import {
|
||||
buildLiveNvidiaProvider,
|
||||
buildNvidiaProvider,
|
||||
buildSelectableLiveNvidiaProvider,
|
||||
} from "./provider-catalog.js";
|
||||
|
||||
const PROVIDER_ID = "nvidia";
|
||||
|
||||
function hasNvidiaApiToken(ctx: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
resolveProviderApiKey?: (providerId?: string) => { apiKey: string | undefined };
|
||||
}) {
|
||||
return Boolean(
|
||||
ctx.resolveProviderApiKey?.(PROVIDER_ID).apiKey?.trim() || ctx.env.NVIDIA_API_KEY?.trim(),
|
||||
);
|
||||
}
|
||||
|
||||
async function buildNvidiaCatalogModels(ctx: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
resolveProviderApiKey?: (providerId?: string) => { apiKey: string | undefined };
|
||||
}) {
|
||||
const provider = hasNvidiaApiToken(ctx) ? await buildLiveNvidiaProvider() : buildNvidiaProvider();
|
||||
return provider.models.map((model) => ({
|
||||
provider: PROVIDER_ID,
|
||||
id: model.id,
|
||||
name: model.name ?? model.id,
|
||||
contextWindow: model.contextWindow,
|
||||
reasoning: model.reasoning,
|
||||
input: model.input,
|
||||
}));
|
||||
}
|
||||
|
||||
export default defineSingleProviderPluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "NVIDIA Provider",
|
||||
description: "Bundled NVIDIA provider plugin",
|
||||
provider: {
|
||||
label: "NVIDIA",
|
||||
docsPath: "/providers/nvidia",
|
||||
envVars: ["NVIDIA_API_KEY"],
|
||||
preserveLiteralProviderPrefix: true,
|
||||
auth: [
|
||||
{
|
||||
methodId: "api-key",
|
||||
label: "NVIDIA API key",
|
||||
hint: "Direct API key",
|
||||
optionKey: "nvidiaApiKey",
|
||||
flagName: "--nvidia-api-key",
|
||||
envVar: "NVIDIA_API_KEY",
|
||||
promptMessage: "Enter NVIDIA API key",
|
||||
defaultModel: NVIDIA_DEFAULT_MODEL_REF,
|
||||
applyConfig: applyNvidiaConfig,
|
||||
},
|
||||
],
|
||||
catalog: {
|
||||
buildProvider: buildSelectableLiveNvidiaProvider,
|
||||
buildStaticProvider: buildNvidiaProvider,
|
||||
},
|
||||
augmentModelCatalog: buildNvidiaCatalogModels,
|
||||
wizard: {
|
||||
setup: {
|
||||
choiceId: "nvidia-api-key",
|
||||
choiceLabel: "NVIDIA API key",
|
||||
groupId: "nvidia",
|
||||
groupLabel: "NVIDIA",
|
||||
groupHint: "Direct API key",
|
||||
methodId: "api-key",
|
||||
modelSelection: {
|
||||
promptWhenAuthChoiceProvided: true,
|
||||
allowKeepCurrent: false,
|
||||
},
|
||||
},
|
||||
modelPicker: {
|
||||
label: "NVIDIA (custom)",
|
||||
hint: "Use NVIDIA-hosted open models",
|
||||
methodId: "api-key",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
56
extensions/nvidia/onboard.test.ts
Normal file
56
extensions/nvidia/onboard.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
// Nvidia tests cover onboard plugin behavior.
|
||||
import {
|
||||
expectProviderOnboardMergedLegacyConfig,
|
||||
expectProviderOnboardPrimaryModel,
|
||||
} from "openclaw/plugin-sdk/provider-test-contracts";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { applyNvidiaConfig, applyNvidiaProviderConfig } from "./onboard.js";
|
||||
|
||||
describe("nvidia onboard", () => {
|
||||
it("adds NVIDIA provider with correct settings", () => {
|
||||
const cfg = applyNvidiaConfig({});
|
||||
const provider = cfg.models?.providers?.nvidia;
|
||||
if (!provider) {
|
||||
throw new Error("expected NVIDIA provider config");
|
||||
}
|
||||
expect(provider.baseUrl).toBe("https://integrate.api.nvidia.com/v1");
|
||||
expect(provider.api).toBe("openai-completions");
|
||||
expect(provider.models.map((model) => model.id)).toEqual([
|
||||
"nvidia/nemotron-3-ultra-550b-a55b",
|
||||
"nvidia/nemotron-3-super-120b-a12b",
|
||||
"moonshotai/kimi-k2.5",
|
||||
"minimaxai/minimax-m2.7",
|
||||
"z-ai/glm-5.1",
|
||||
"minimaxai/minimax-m2.5",
|
||||
"z-ai/glm5",
|
||||
]);
|
||||
// Config stores the canonical form; the picker label shows the literal
|
||||
// form via preserveLiteralProviderPrefix.
|
||||
expectProviderOnboardPrimaryModel({
|
||||
applyConfig: applyNvidiaConfig,
|
||||
modelRef: "nvidia/nemotron-3-ultra-550b-a55b",
|
||||
});
|
||||
});
|
||||
|
||||
it("merges NVIDIA models and keeps existing provider overrides", () => {
|
||||
const provider = expectProviderOnboardMergedLegacyConfig({
|
||||
applyProviderConfig: applyNvidiaProviderConfig,
|
||||
providerId: "nvidia",
|
||||
providerApi: "openai-completions",
|
||||
baseUrl: "https://integrate.api.nvidia.com/v1",
|
||||
legacyApi: "openai-completions",
|
||||
legacyModelId: "custom-model",
|
||||
legacyModelName: "Custom",
|
||||
});
|
||||
expect(provider?.models.map((model) => model.id)).toEqual([
|
||||
"nvidia/custom-model",
|
||||
"nvidia/nemotron-3-ultra-550b-a55b",
|
||||
"nvidia/nemotron-3-super-120b-a12b",
|
||||
"moonshotai/kimi-k2.5",
|
||||
"minimaxai/minimax-m2.7",
|
||||
"z-ai/glm-5.1",
|
||||
"minimaxai/minimax-m2.5",
|
||||
"z-ai/glm5",
|
||||
]);
|
||||
});
|
||||
});
|
||||
31
extensions/nvidia/onboard.ts
Normal file
31
extensions/nvidia/onboard.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
// Nvidia setup module handles plugin onboarding behavior.
|
||||
import {
|
||||
createDefaultModelsPresetAppliers,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/provider-onboard";
|
||||
import { buildNvidiaProvider, NVIDIA_DEFAULT_MODEL_ID } from "./provider-catalog.js";
|
||||
|
||||
export const NVIDIA_DEFAULT_MODEL_REF = NVIDIA_DEFAULT_MODEL_ID;
|
||||
|
||||
const nvidiaPresetAppliers = createDefaultModelsPresetAppliers({
|
||||
primaryModelRef: NVIDIA_DEFAULT_MODEL_REF,
|
||||
resolveParams: (_cfg: OpenClawConfig) => {
|
||||
const defaultProvider = buildNvidiaProvider();
|
||||
return {
|
||||
providerId: "nvidia",
|
||||
api: defaultProvider.api ?? "openai-completions",
|
||||
baseUrl: defaultProvider.baseUrl,
|
||||
defaultModels: defaultProvider.models ?? [],
|
||||
defaultModelId: NVIDIA_DEFAULT_MODEL_ID,
|
||||
aliases: [{ modelRef: NVIDIA_DEFAULT_MODEL_REF, alias: "NVIDIA" }],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export function applyNvidiaProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||
return nvidiaPresetAppliers.applyProviderConfig(cfg);
|
||||
}
|
||||
|
||||
export function applyNvidiaConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||
return nvidiaPresetAppliers.applyConfig(cfg);
|
||||
}
|
||||
182
extensions/nvidia/openclaw.plugin.json
Normal file
182
extensions/nvidia/openclaw.plugin.json
Normal file
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"id": "nvidia",
|
||||
"icon": "https://cdn.simpleicons.org/nvidia",
|
||||
"activation": {
|
||||
"onStartup": false
|
||||
},
|
||||
"enabledByDefault": true,
|
||||
"providers": ["nvidia"],
|
||||
"providerEndpoints": [
|
||||
{
|
||||
"endpointClass": "nvidia-native",
|
||||
"hosts": ["integrate.api.nvidia.com"],
|
||||
"baseUrls": ["https://integrate.api.nvidia.com/v1"]
|
||||
}
|
||||
],
|
||||
"modelIdNormalization": {
|
||||
"providers": {
|
||||
"nvidia": {
|
||||
"prefixWhenBare": "nvidia"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modelCatalog": {
|
||||
"providers": {
|
||||
"nvidia": {
|
||||
"baseUrl": "https://integrate.api.nvidia.com/v1",
|
||||
"api": "openai-completions",
|
||||
"models": [
|
||||
{
|
||||
"id": "nvidia/nemotron-3-ultra-550b-a55b",
|
||||
"name": "NVIDIA Nemotron 3 Ultra 550B",
|
||||
"input": ["text"],
|
||||
"contextWindow": 1000000,
|
||||
"maxTokens": 16384,
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"requiresStringContent": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "nvidia/nemotron-3-super-120b-a12b",
|
||||
"name": "NVIDIA Nemotron 3 Super 120B",
|
||||
"input": ["text"],
|
||||
"contextWindow": 1048576,
|
||||
"maxTokens": 8192,
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"requiresStringContent": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "moonshotai/kimi-k2.5",
|
||||
"name": "Kimi K2.5",
|
||||
"input": ["text"],
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 8192,
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"requiresStringContent": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "minimaxai/minimax-m2.7",
|
||||
"name": "Minimax M2.7",
|
||||
"input": ["text"],
|
||||
"contextWindow": 196608,
|
||||
"maxTokens": 8192,
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"requiresStringContent": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "z-ai/glm-5.1",
|
||||
"name": "GLM 5.1",
|
||||
"input": ["text"],
|
||||
"contextWindow": 202752,
|
||||
"maxTokens": 8192,
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"requiresStringContent": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "minimaxai/minimax-m2.5",
|
||||
"name": "MiniMax M2.5",
|
||||
"input": ["text"],
|
||||
"contextWindow": 196608,
|
||||
"maxTokens": 8192,
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"requiresStringContent": true
|
||||
},
|
||||
"status": "deprecated",
|
||||
"statusReason": "Shipped compatibility row; use minimaxai/minimax-m2.7 for new NVIDIA setups.",
|
||||
"replacedBy": "minimaxai/minimax-m2.7"
|
||||
},
|
||||
{
|
||||
"id": "z-ai/glm5",
|
||||
"name": "GLM-5",
|
||||
"input": ["text"],
|
||||
"contextWindow": 202752,
|
||||
"maxTokens": 8192,
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
},
|
||||
"compat": {
|
||||
"requiresStringContent": true
|
||||
},
|
||||
"status": "deprecated",
|
||||
"statusReason": "Shipped compatibility row; use z-ai/glm-5.1 for new NVIDIA setups.",
|
||||
"replacedBy": "z-ai/glm-5.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"discovery": {
|
||||
"nvidia": "static"
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"providers": [
|
||||
{
|
||||
"id": "nvidia",
|
||||
"envVars": ["NVIDIA_API_KEY"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"providerAuthChoices": [
|
||||
{
|
||||
"provider": "nvidia",
|
||||
"method": "api-key",
|
||||
"choiceId": "nvidia-api-key",
|
||||
"choiceLabel": "NVIDIA API key",
|
||||
"groupId": "nvidia",
|
||||
"groupLabel": "NVIDIA",
|
||||
"groupHint": "Direct API key",
|
||||
"optionKey": "nvidiaApiKey",
|
||||
"cliFlag": "--nvidia-api-key",
|
||||
"cliOption": "--nvidia-api-key <key>",
|
||||
"cliDescription": "NVIDIA API key"
|
||||
}
|
||||
],
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
15
extensions/nvidia/package.json
Normal file
15
extensions/nvidia/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@openclaw/nvidia-provider",
|
||||
"version": "2026.6.11",
|
||||
"private": true,
|
||||
"description": "OpenClaw NVIDIA provider plugin",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
325
extensions/nvidia/provider-catalog.test.ts
Normal file
325
extensions/nvidia/provider-catalog.test.ts
Normal file
@@ -0,0 +1,325 @@
|
||||
// Nvidia tests cover provider catalog plugin behavior.
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
buildLiveNvidiaProvider,
|
||||
buildNvidiaProvider,
|
||||
buildSelectableLiveNvidiaProvider,
|
||||
clearNvidiaFeaturedModelCacheForTests,
|
||||
NVIDIA_FEATURED_MODELS_URL,
|
||||
} from "./provider-catalog.js";
|
||||
|
||||
const ssrfRuntimeMocks = vi.hoisted(() => ({
|
||||
fetchWithSsrFGuard: vi.fn(),
|
||||
ssrfPolicyFromHttpBaseUrlAllowedHostname: vi.fn((baseUrl: string) => ({
|
||||
allowedHostnames: [new URL(baseUrl).hostname],
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ssrfRuntimeMocks);
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
clearNvidiaFeaturedModelCacheForTests();
|
||||
ssrfRuntimeMocks.fetchWithSsrFGuard.mockReset();
|
||||
ssrfRuntimeMocks.ssrfPolicyFromHttpBaseUrlAllowedHostname.mockClear();
|
||||
});
|
||||
|
||||
function mockFeaturedCatalogResponse(payload: unknown, status = 200) {
|
||||
const release = vi.fn();
|
||||
ssrfRuntimeMocks.fetchWithSsrFGuard.mockResolvedValueOnce({
|
||||
response: Response.json(payload, { status }),
|
||||
finalUrl: NVIDIA_FEATURED_MODELS_URL,
|
||||
release,
|
||||
});
|
||||
return release;
|
||||
}
|
||||
|
||||
describe("nvidia provider catalog", () => {
|
||||
it("builds the bundled NVIDIA provider defaults", () => {
|
||||
const provider = buildNvidiaProvider();
|
||||
|
||||
expect(provider.baseUrl).toBe("https://integrate.api.nvidia.com/v1");
|
||||
expect(provider.api).toBe("openai-completions");
|
||||
expect(provider.apiKey).toBe("NVIDIA_API_KEY");
|
||||
expect(provider.models.map((model) => model.id)).toEqual([
|
||||
"nvidia/nemotron-3-ultra-550b-a55b",
|
||||
"nvidia/nemotron-3-super-120b-a12b",
|
||||
"moonshotai/kimi-k2.5",
|
||||
"minimaxai/minimax-m2.7",
|
||||
"z-ai/glm-5.1",
|
||||
"minimaxai/minimax-m2.5",
|
||||
"z-ai/glm5",
|
||||
]);
|
||||
expect(provider.models.filter((model) => model.compat?.requiresStringContent !== true)).toEqual(
|
||||
[],
|
||||
);
|
||||
expect(provider.models[0]).toMatchObject({
|
||||
contextWindow: 1_000_000,
|
||||
maxTokens: 16_384,
|
||||
params: {
|
||||
chat_template_kwargs: {
|
||||
enable_thinking: false,
|
||||
force_nonempty_content: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(provider.models[1]).toMatchObject({
|
||||
id: "nvidia/nemotron-3-super-120b-a12b",
|
||||
contextWindow: 1_048_576,
|
||||
});
|
||||
});
|
||||
|
||||
it("promotes ranked models from NVIDIA's featured catalog", async () => {
|
||||
const release = mockFeaturedCatalogResponse({
|
||||
"featured-models": [
|
||||
{
|
||||
model: "z-ai/glm-5.1",
|
||||
"model-name": "GLM 5.1",
|
||||
context: 202752,
|
||||
"max-output": 8192,
|
||||
},
|
||||
{
|
||||
model: "nemotron-3-super-120b-a12b",
|
||||
"model-name": "Nemotron 3 Super 120B",
|
||||
context: 262144,
|
||||
"max-output": 8192,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const provider = await buildLiveNvidiaProvider();
|
||||
|
||||
expect(provider.models.map((model) => model.id)).toEqual([
|
||||
"z-ai/glm-5.1",
|
||||
"nvidia/nemotron-3-super-120b-a12b",
|
||||
]);
|
||||
expect(provider.models[0]).toMatchObject({
|
||||
name: "GLM 5.1",
|
||||
contextWindow: 202752,
|
||||
maxTokens: 8192,
|
||||
compat: { requiresStringContent: true },
|
||||
});
|
||||
expect(ssrfRuntimeMocks.fetchWithSsrFGuard).toHaveBeenCalledWith({
|
||||
auditContext: "nvidia-featured-model-catalog",
|
||||
init: { headers: expect.any(Headers) },
|
||||
lookupFn: expect.any(Function),
|
||||
policy: { allowedHostnames: ["assets.ngc.nvidia.com"] },
|
||||
signal: undefined,
|
||||
timeoutMs: 10_000,
|
||||
url: NVIDIA_FEATURED_MODELS_URL,
|
||||
requireHttps: true,
|
||||
});
|
||||
expect(release).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("falls back to the bundled catalog when the featured catalog is unavailable", async () => {
|
||||
mockFeaturedCatalogResponse({ error: "unavailable" }, 503);
|
||||
|
||||
const provider = await buildLiveNvidiaProvider();
|
||||
|
||||
expect(provider.models.map((model) => model.id)).toEqual([
|
||||
"nvidia/nemotron-3-ultra-550b-a55b",
|
||||
"nvidia/nemotron-3-super-120b-a12b",
|
||||
"moonshotai/kimi-k2.5",
|
||||
"minimaxai/minimax-m2.7",
|
||||
"z-ai/glm-5.1",
|
||||
"minimaxai/minimax-m2.5",
|
||||
"z-ai/glm5",
|
||||
]);
|
||||
});
|
||||
|
||||
it("retains shipped NVIDIA model refs as bundled fallback compatibility rows", () => {
|
||||
const provider = buildNvidiaProvider();
|
||||
|
||||
expect(provider.models.map((model) => model.id)).toEqual(
|
||||
expect.arrayContaining(["minimaxai/minimax-m2.5", "z-ai/glm5"]),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses only selectable live catalog rows when the featured catalog returns models", async () => {
|
||||
mockFeaturedCatalogResponse({
|
||||
"featured-models": [
|
||||
{
|
||||
model: "z-ai/glm-5.1",
|
||||
"model-name": "GLM 5.1",
|
||||
context: 202752,
|
||||
"max-output": 8192,
|
||||
},
|
||||
{
|
||||
model: "nemotron-3-super-120b-a12b",
|
||||
"model-name": "Nemotron 3 Super 120B",
|
||||
context: 262144,
|
||||
"max-output": 8192,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const provider = await buildSelectableLiveNvidiaProvider();
|
||||
|
||||
expect(provider.models.map((model) => model.id)).toEqual([
|
||||
"z-ai/glm-5.1",
|
||||
"nvidia/nemotron-3-super-120b-a12b",
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns no selectable live rows when the featured catalog is unavailable", async () => {
|
||||
mockFeaturedCatalogResponse({ error: "unavailable" }, 503);
|
||||
|
||||
const provider = await buildSelectableLiveNvidiaProvider();
|
||||
|
||||
expect(provider.models.map((model) => model.id)).toEqual([]);
|
||||
});
|
||||
|
||||
it("ignores malformed featured catalog rows and keeps valid entries", async () => {
|
||||
mockFeaturedCatalogResponse({
|
||||
"featured-models": [
|
||||
{
|
||||
model: "bad model id",
|
||||
"model-name": "Bad",
|
||||
context: 1000,
|
||||
"max-output": 1000,
|
||||
},
|
||||
{
|
||||
model: "minimaxai/minimax-m2.7",
|
||||
"model-name": "Minimax M2.7",
|
||||
context: 196608,
|
||||
"max-output": 8192,
|
||||
},
|
||||
{
|
||||
model: "oversized-context",
|
||||
"model-name": "Oversized Context",
|
||||
context: 10_000_001,
|
||||
"max-output": 8192,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const provider = await buildLiveNvidiaProvider();
|
||||
|
||||
expect(provider.models.map((model) => model.id)).toEqual(["minimaxai/minimax-m2.7"]);
|
||||
});
|
||||
|
||||
it("caches the featured catalog for repeated provider builds", async () => {
|
||||
mockFeaturedCatalogResponse({
|
||||
"featured-models": [
|
||||
{
|
||||
model: "minimaxai/minimax-m2.7",
|
||||
"model-name": "Minimax M2.7",
|
||||
context: 196608,
|
||||
"max-output": 8192,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await buildLiveNvidiaProvider();
|
||||
await buildLiveNvidiaProvider();
|
||||
|
||||
expect(ssrfRuntimeMocks.fetchWithSsrFGuard).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("skips featured catalog cache when ttl expiry overflows", async () => {
|
||||
vi.setSystemTime(new Date(8_640_000_000_000_000));
|
||||
mockFeaturedCatalogResponse({
|
||||
"featured-models": [
|
||||
{
|
||||
model: "minimaxai/minimax-m2.7",
|
||||
"model-name": "Minimax M2.7",
|
||||
context: 196608,
|
||||
"max-output": 8192,
|
||||
},
|
||||
],
|
||||
});
|
||||
mockFeaturedCatalogResponse({
|
||||
"featured-models": [
|
||||
{
|
||||
model: "z-ai/glm-5.1",
|
||||
"model-name": "GLM 5.1",
|
||||
context: 202752,
|
||||
"max-output": 8192,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const first = await buildLiveNvidiaProvider();
|
||||
const second = await buildLiveNvidiaProvider();
|
||||
|
||||
expect(first.models.map((model) => model.id)).toEqual(["minimaxai/minimax-m2.7"]);
|
||||
expect(second.models.map((model) => model.id)).toEqual(["z-ai/glm-5.1"]);
|
||||
expect(ssrfRuntimeMocks.fetchWithSsrFGuard).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("does not cache successful featured catalog responses with no usable rows", async () => {
|
||||
mockFeaturedCatalogResponse({
|
||||
"featured-models": [
|
||||
{
|
||||
model: "bad model id",
|
||||
"model-name": "Bad",
|
||||
context: 1000,
|
||||
"max-output": 1000,
|
||||
},
|
||||
],
|
||||
});
|
||||
mockFeaturedCatalogResponse({
|
||||
"featured-models": [
|
||||
{
|
||||
model: "z-ai/glm-5.1",
|
||||
"model-name": "GLM 5.1",
|
||||
context: 202752,
|
||||
"max-output": 8192,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const first = await buildLiveNvidiaProvider();
|
||||
const second = await buildLiveNvidiaProvider();
|
||||
|
||||
expect(first.models.map((model) => model.id)).toEqual([
|
||||
"nvidia/nemotron-3-ultra-550b-a55b",
|
||||
"nvidia/nemotron-3-super-120b-a12b",
|
||||
"moonshotai/kimi-k2.5",
|
||||
"minimaxai/minimax-m2.7",
|
||||
"z-ai/glm-5.1",
|
||||
"minimaxai/minimax-m2.5",
|
||||
"z-ai/glm5",
|
||||
]);
|
||||
expect(second.models.map((model) => model.id)).toEqual(["z-ai/glm-5.1"]);
|
||||
expect(ssrfRuntimeMocks.fetchWithSsrFGuard).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("applies bundled Ultra defaults when featured catalog returns Ultra", async () => {
|
||||
mockFeaturedCatalogResponse({
|
||||
"featured-models": [
|
||||
{
|
||||
model: "nemotron-3-ultra-550b-a55b",
|
||||
"model-name": "Nemotron 3 Ultra 550B",
|
||||
context: 1000000,
|
||||
"max-output": 16384,
|
||||
},
|
||||
{
|
||||
model: "minimaxai/minimax-m2.7",
|
||||
"model-name": "Minimax M2.7",
|
||||
context: 196608,
|
||||
"max-output": 8192,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const provider = await buildLiveNvidiaProvider();
|
||||
|
||||
expect(provider.models.map((model) => model.id)).toEqual([
|
||||
"nvidia/nemotron-3-ultra-550b-a55b",
|
||||
"minimaxai/minimax-m2.7",
|
||||
]);
|
||||
expect(provider.models[0]).toMatchObject({
|
||||
name: "Nemotron 3 Ultra 550B",
|
||||
contextWindow: 1_000_000,
|
||||
maxTokens: 16_384,
|
||||
params: {
|
||||
chat_template_kwargs: {
|
||||
enable_thinking: false,
|
||||
force_nonempty_content: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
251
extensions/nvidia/provider-catalog.ts
Normal file
251
extensions/nvidia/provider-catalog.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
// Nvidia provider module implements model/runtime integration.
|
||||
import { lookup as dnsLookup } from "node:dns/promises";
|
||||
import {
|
||||
clearLiveCatalogCacheForTests,
|
||||
getCachedLiveProviderModelRows,
|
||||
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
||||
import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared";
|
||||
import type {
|
||||
ModelDefinitionConfig,
|
||||
ModelProviderConfig,
|
||||
} from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import {
|
||||
type LookupFn,
|
||||
ssrfPolicyFromHttpBaseUrlAllowedHostname,
|
||||
} from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import manifest from "./openclaw.plugin.json" with { type: "json" };
|
||||
|
||||
export const NVIDIA_DEFAULT_MODEL_ID = "nvidia/nemotron-3-ultra-550b-a55b";
|
||||
export const NVIDIA_FEATURED_MODELS_URL =
|
||||
"https://assets.ngc.nvidia.com/products/api-catalog/featured-models.json";
|
||||
|
||||
const FEATURED_MODEL_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
||||
const FEATURED_MODEL_FETCH_TIMEOUT_MS = 10_000;
|
||||
const FEATURED_MODEL_MAX_ROWS = 32;
|
||||
const FEATURED_MODEL_MAX_ID_LENGTH = 200;
|
||||
const FEATURED_MODEL_MAX_NAME_LENGTH = 200;
|
||||
const FEATURED_MODEL_MAX_CONTEXT_WINDOW = 10_000_000;
|
||||
const FEATURED_MODEL_MAX_OUTPUT_TOKENS = 1_000_000;
|
||||
const FEATURED_MODEL_COST = {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
} as const;
|
||||
const NVIDIA_ULTRA_DEFAULT_PARAMS = {
|
||||
chat_template_kwargs: {
|
||||
enable_thinking: false,
|
||||
force_nonempty_content: true,
|
||||
},
|
||||
} as const;
|
||||
|
||||
type NvidiaFeaturedModel = {
|
||||
model: string;
|
||||
"model-name": string;
|
||||
context: number;
|
||||
"max-output": number;
|
||||
};
|
||||
|
||||
type DnsLookupOptions = {
|
||||
all?: boolean;
|
||||
family?: number;
|
||||
hints?: number;
|
||||
order?: "ipv4first" | "ipv6first" | "verbatim";
|
||||
verbatim?: boolean;
|
||||
};
|
||||
|
||||
const lookupNvidiaFeaturedModelHostname = (async (
|
||||
hostname: string,
|
||||
options?: number | DnsLookupOptions,
|
||||
) => {
|
||||
if (typeof options === "object" && options !== null) {
|
||||
return await dnsLookup(hostname, { ...options, family: 4 });
|
||||
}
|
||||
return await dnsLookup(hostname, { family: 4 });
|
||||
}) as LookupFn;
|
||||
|
||||
export function buildNvidiaProvider(): ModelProviderConfig {
|
||||
const provider = {
|
||||
...buildManifestModelProviderConfig({
|
||||
providerId: "nvidia",
|
||||
catalog: manifest.modelCatalog.providers.nvidia,
|
||||
}),
|
||||
apiKey: "NVIDIA_API_KEY",
|
||||
};
|
||||
return {
|
||||
...provider,
|
||||
models: applyNvidiaModelDefaults(provider.models ?? []),
|
||||
};
|
||||
}
|
||||
|
||||
export async function buildLiveNvidiaProvider(): Promise<ModelProviderConfig> {
|
||||
const provider = buildNvidiaProvider();
|
||||
const featuredModels = await loadNvidiaFeaturedModels();
|
||||
if (!featuredModels || featuredModels.length === 0) {
|
||||
return provider;
|
||||
}
|
||||
return {
|
||||
...provider,
|
||||
models: applyNvidiaModelDefaults(featuredModels),
|
||||
};
|
||||
}
|
||||
|
||||
export async function buildSelectableLiveNvidiaProvider(): Promise<ModelProviderConfig> {
|
||||
const provider = buildNvidiaProvider();
|
||||
const featuredModels = await loadNvidiaFeaturedModels();
|
||||
if (!featuredModels || featuredModels.length === 0) {
|
||||
return {
|
||||
...provider,
|
||||
models: [],
|
||||
};
|
||||
}
|
||||
return {
|
||||
...provider,
|
||||
models: applyNvidiaModelDefaults(featuredModels),
|
||||
};
|
||||
}
|
||||
|
||||
export function clearNvidiaFeaturedModelCacheForTests() {
|
||||
clearLiveCatalogCacheForTests();
|
||||
}
|
||||
|
||||
async function loadNvidiaFeaturedModels(): Promise<ModelDefinitionConfig[] | null> {
|
||||
try {
|
||||
const rows = await getCachedLiveProviderModelRows({
|
||||
providerId: "nvidia",
|
||||
endpoint: NVIDIA_FEATURED_MODELS_URL,
|
||||
timeoutMs: FEATURED_MODEL_FETCH_TIMEOUT_MS,
|
||||
ttlMs: FEATURED_MODEL_CACHE_TTL_MS,
|
||||
requireHttps: true,
|
||||
policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(NVIDIA_FEATURED_MODELS_URL),
|
||||
// The featured catalog is an NVIDIA-owned CloudFront URL. Some resolvers
|
||||
// stall for seconds on the default all-family lookup; IPv4 pinning keeps
|
||||
// the guarded fixed-host fetch on the fast path.
|
||||
lookupFn: lookupNvidiaFeaturedModelHostname,
|
||||
auditContext: "nvidia-featured-model-catalog",
|
||||
shouldCacheRows: (modelRows) => parseNvidiaFeaturedModels(modelRows) !== null,
|
||||
readRows: (payload) => {
|
||||
if (!payload || typeof payload !== "object") {
|
||||
return [];
|
||||
}
|
||||
const featuredRows = (payload as { "featured-models"?: unknown })["featured-models"];
|
||||
return Array.isArray(featuredRows) ? featuredRows : [];
|
||||
},
|
||||
});
|
||||
return parseNvidiaFeaturedModels(rows);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parseNvidiaFeaturedModels(rows: readonly unknown[]): ModelDefinitionConfig[] | null {
|
||||
const models = rows
|
||||
.slice(0, FEATURED_MODEL_MAX_ROWS)
|
||||
.map(parseNvidiaFeaturedModel)
|
||||
.filter((model) => model !== null);
|
||||
return models.length > 0 ? models : null;
|
||||
}
|
||||
|
||||
function applyNvidiaModelDefaults(models: ModelDefinitionConfig[]): ModelDefinitionConfig[] {
|
||||
return models.map((model) =>
|
||||
model.id === NVIDIA_DEFAULT_MODEL_ID
|
||||
? {
|
||||
...model,
|
||||
params: {
|
||||
...model.params,
|
||||
chat_template_kwargs: {
|
||||
...NVIDIA_ULTRA_DEFAULT_PARAMS.chat_template_kwargs,
|
||||
...(isRecord(model.params?.chat_template_kwargs)
|
||||
? model.params.chat_template_kwargs
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
}
|
||||
: model,
|
||||
);
|
||||
}
|
||||
|
||||
function parseNvidiaFeaturedModel(row: unknown): ModelDefinitionConfig | null {
|
||||
if (!row || typeof row !== "object") {
|
||||
return null;
|
||||
}
|
||||
const entry = row as Partial<NvidiaFeaturedModel>;
|
||||
if (
|
||||
typeof entry.model !== "string" ||
|
||||
typeof entry["model-name"] !== "string" ||
|
||||
!isBoundedPositiveInteger(entry.context, FEATURED_MODEL_MAX_CONTEXT_WINDOW) ||
|
||||
!isBoundedPositiveInteger(entry["max-output"], FEATURED_MODEL_MAX_OUTPUT_TOKENS)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const id = normalizeNvidiaFeaturedModelId(entry.model);
|
||||
const name = normalizeFeaturedModelName(entry["model-name"]);
|
||||
if (!id || !name) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
contextWindow: entry.context,
|
||||
maxTokens: entry["max-output"],
|
||||
cost: { ...FEATURED_MODEL_COST },
|
||||
compat: {
|
||||
requiresStringContent: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeNvidiaFeaturedModelId(model: string): string {
|
||||
const trimmed = model.trim();
|
||||
if (
|
||||
!trimmed ||
|
||||
trimmed.length > FEATURED_MODEL_MAX_ID_LENGTH ||
|
||||
hasWhitespaceOrControlCharacter(trimmed)
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
return trimmed.includes("/") ? trimmed : `nvidia/${trimmed}`;
|
||||
}
|
||||
|
||||
function normalizeFeaturedModelName(name: string): string {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed || trimmed.length > FEATURED_MODEL_MAX_NAME_LENGTH || hasControlCharacter(trimmed)) {
|
||||
return "";
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function isBoundedPositiveInteger(value: unknown, max: number): value is number {
|
||||
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= max;
|
||||
}
|
||||
|
||||
function hasWhitespaceOrControlCharacter(value: string): boolean {
|
||||
for (const char of value) {
|
||||
if (isAsciiWhitespaceOrControlCharacter(char)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasControlCharacter(value: string): boolean {
|
||||
for (const char of value) {
|
||||
if (isControlCharacter(char)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isControlCharacter(char: string): boolean {
|
||||
const code = char.charCodeAt(0);
|
||||
return code <= 31 || code === 127;
|
||||
}
|
||||
|
||||
function isAsciiWhitespaceOrControlCharacter(char: string): boolean {
|
||||
const code = char.charCodeAt(0);
|
||||
return code <= 32 || code === 127;
|
||||
}
|
||||
16
extensions/nvidia/tsconfig.json
Normal file
16
extensions/nvidia/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../tsconfig.package-boundary.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["./*.ts", "./src/**/*.ts"],
|
||||
"exclude": [
|
||||
"./**/*.test.ts",
|
||||
"./dist/**",
|
||||
"./node_modules/**",
|
||||
"./src/test-support/**",
|
||||
"./src/**/*test-helpers.ts",
|
||||
"./src/**/*test-harness.ts",
|
||||
"./src/**/*test-support.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user