Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 4 additions & 140 deletions bin/lib/inference-config.js
Original file line number Diff line number Diff line change
@@ -1,143 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Thin re-export shim — the implementation lives in src/lib/inference-config.ts,
// compiled to dist/lib/inference-config.js.

const INFERENCE_ROUTE_URL = "https://inference.local/v1";
const DEFAULT_CLOUD_MODEL = "nvidia/nemotron-3-super-120b-a12b";
const CLOUD_MODEL_OPTIONS = [
{ id: "nvidia/nemotron-3-super-120b-a12b", label: "Nemotron 3 Super 120B" },
{ id: "moonshotai/kimi-k2.5", label: "Kimi K2.5" },
{ id: "z-ai/glm5", label: "GLM-5" },
{ id: "minimaxai/minimax-m2.5", label: "MiniMax M2.5" },
{ id: "qwen/qwen3.5-397b-a17b", label: "Qwen3.5 397B A17B" },
{ id: "openai/gpt-oss-120b", label: "GPT-OSS 120B" },
];
const DEFAULT_ROUTE_PROFILE = "inference-local";
const DEFAULT_ROUTE_CREDENTIAL_ENV = "OPENAI_API_KEY";
const MANAGED_PROVIDER_ID = "inference";
const { DEFAULT_OLLAMA_MODEL } = require("./local-inference");

function getProviderSelectionConfig(provider, model) {
switch (provider) {
case "nvidia-prod":
case "nvidia-nim":
return {
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: model || DEFAULT_CLOUD_MODEL,
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: DEFAULT_ROUTE_CREDENTIAL_ENV,
provider,
providerLabel: "NVIDIA Endpoints",
};
case "openai-api":
return {
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: model || "gpt-5.4",
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: "OPENAI_API_KEY",
provider,
providerLabel: "OpenAI",
};
case "anthropic-prod":
return {
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: model || "claude-sonnet-4-6",
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: "ANTHROPIC_API_KEY",
provider,
providerLabel: "Anthropic",
};
case "compatible-anthropic-endpoint":
return {
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: model || "custom-anthropic-model",
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: "COMPATIBLE_ANTHROPIC_API_KEY",
provider,
providerLabel: "Other Anthropic-compatible endpoint",
};
case "gemini-api":
return {
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: model || "gemini-2.5-flash",
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: "GEMINI_API_KEY",
provider,
providerLabel: "Google Gemini",
};
case "compatible-endpoint":
return {
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: model || "custom-model",
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: "COMPATIBLE_API_KEY",
provider,
providerLabel: "Other OpenAI-compatible endpoint",
};
case "vllm-local":
return {
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: model || "vllm-local",
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: DEFAULT_ROUTE_CREDENTIAL_ENV,
provider,
providerLabel: "Local vLLM",
};
case "ollama-local":
return {
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: model || DEFAULT_OLLAMA_MODEL,
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: DEFAULT_ROUTE_CREDENTIAL_ENV,
provider,
providerLabel: "Local Ollama",
};
default:
return null;
}
}

function getOpenClawPrimaryModel(provider, model) {
const resolvedModel =
model || (provider === "ollama-local" ? DEFAULT_OLLAMA_MODEL : DEFAULT_CLOUD_MODEL);
return resolvedModel ? `${MANAGED_PROVIDER_ID}/${resolvedModel}` : null;
}

function parseGatewayInference(output) {
if (!output || /Not configured/i.test(output)) return null;
const provider = output.match(/Provider:\s*(.+)/);
const model = output.match(/Model:\s*(.+)/);
if (!provider && !model) return null;
return {
provider: provider ? provider[1].trim() : null,
model: model ? model[1].trim() : null,
};
}

module.exports = {
CLOUD_MODEL_OPTIONS,
DEFAULT_CLOUD_MODEL,
DEFAULT_OLLAMA_MODEL,
DEFAULT_ROUTE_CREDENTIAL_ENV,
DEFAULT_ROUTE_PROFILE,
INFERENCE_ROUTE_URL,
MANAGED_PROVIDER_ID,
getOpenClawPrimaryModel,
getProviderSelectionConfig,
parseGatewayInference,
};
module.exports = require("../../dist/lib/inference-config");
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"typecheck": "tsc -p jsconfig.json",
"build:cli": "tsc -p tsconfig.src.json",
"typecheck:cli": "tsc -p tsconfig.cli.json",
"prepare": "npm install --omit=dev --ignore-scripts 2>/dev/null || true && if [ -d .git ]; then if command -v prek >/dev/null 2>&1; then prek install; elif [ -d node_modules/@j178/prek ]; then echo \"ERROR: prek package found but binary not in PATH\" && exit 1; else echo \"Skipping git hook setup (prek not installed)\"; fi; fi",
"prepare": "npm install --omit=dev --ignore-scripts 2>/dev/null || true && if command -v tsc >/dev/null 2>&1 || [ -x node_modules/.bin/tsc ]; then npm run build:cli; fi && if [ -d .git ]; then if command -v prek >/dev/null 2>&1; then prek install; elif [ -d node_modules/@j178/prek ]; then echo \"ERROR: prek package found but binary not in PATH\" && exit 1; else echo \"Skipping git hook setup (prek not installed)\"; fi; fi",
"prepublishOnly": "cd nemoclaw && env -u npm_config_global -u npm_config_prefix -u npm_config_omit npm install --ignore-scripts && ./node_modules/.bin/tsc"
},
"dependencies": {
Expand Down
140 changes: 43 additions & 97 deletions test/inference-config.test.js → src/lib/inference-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import assert from "node:assert/strict";
import { describe, it, expect } from "vitest";

// Import from compiled dist/ for correct coverage attribution.
import {
CLOUD_MODEL_OPTIONS,
DEFAULT_OLLAMA_MODEL,
Expand All @@ -14,11 +14,11 @@ import {
getOpenClawPrimaryModel,
getProviderSelectionConfig,
parseGatewayInference,
} from "../bin/lib/inference-config";
} from "../../dist/lib/inference-config";

describe("inference selection config", () => {
it("exposes the curated cloud model picker options", () => {
expect(CLOUD_MODEL_OPTIONS.map((option) => option.id)).toEqual([
expect(CLOUD_MODEL_OPTIONS.map((option: { id: string }) => option.id)).toEqual([
"nvidia/nemotron-3-super-120b-a12b",
"moonshotai/kimi-k2.5",
"z-ai/glm5",
Expand Down Expand Up @@ -55,22 +55,22 @@ describe("inference selection config", () => {
});

it("maps compatible-anthropic-endpoint to the sandbox inference route", () => {
assert.deepEqual(
expect(
getProviderSelectionConfig("compatible-anthropic-endpoint", "claude-sonnet-proxy"),
{
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: "claude-sonnet-proxy",
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: "COMPATIBLE_ANTHROPIC_API_KEY",
provider: "compatible-anthropic-endpoint",
providerLabel: "Other Anthropic-compatible endpoint",
},
);
).toEqual({
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: "claude-sonnet-proxy",
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: "COMPATIBLE_ANTHROPIC_API_KEY",
provider: "compatible-anthropic-endpoint",
providerLabel: "Other Anthropic-compatible endpoint",
});
});

it("maps the remaining hosted providers to the sandbox inference route", () => {
// Full-object assertion for one hosted provider to catch structural regressions
expect(getProviderSelectionConfig("openai-api", "gpt-5.4-mini")).toEqual({
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
Expand All @@ -81,40 +81,19 @@ describe("inference selection config", () => {
provider: "openai-api",
providerLabel: "OpenAI",
});

expect(getProviderSelectionConfig("anthropic-prod", "claude-sonnet-4-6")).toEqual({
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: "claude-sonnet-4-6",
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: "ANTHROPIC_API_KEY",
provider: "anthropic-prod",
providerLabel: "Anthropic",
});

expect(getProviderSelectionConfig("gemini-api", "gemini-2.5-pro")).toEqual({
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: "gemini-2.5-pro",
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: "GEMINI_API_KEY",
provider: "gemini-api",
providerLabel: "Google Gemini",
});

expect(getProviderSelectionConfig("compatible-endpoint", "openrouter/auto")).toEqual({
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
ncpPartner: null,
model: "openrouter/auto",
profile: DEFAULT_ROUTE_PROFILE,
credentialEnv: "COMPATIBLE_API_KEY",
provider: "compatible-endpoint",
providerLabel: "Other OpenAI-compatible endpoint",
});

expect(getProviderSelectionConfig("anthropic-prod", "claude-sonnet-4-6")).toEqual(
expect.objectContaining({ model: "claude-sonnet-4-6", providerLabel: "Anthropic" }),
);
expect(getProviderSelectionConfig("gemini-api", "gemini-2.5-pro")).toEqual(
expect.objectContaining({ model: "gemini-2.5-pro", providerLabel: "Google Gemini" }),
);
expect(getProviderSelectionConfig("compatible-endpoint", "openrouter/auto")).toEqual(
expect.objectContaining({
model: "openrouter/auto",
providerLabel: "Other OpenAI-compatible endpoint",
}),
);
// Full-object assertion for one local provider
expect(getProviderSelectionConfig("vllm-local", "meta-llama")).toEqual({
endpointType: "custom",
endpointUrl: INFERENCE_ROUTE_URL,
Expand All @@ -131,10 +110,6 @@ describe("inference selection config", () => {
expect(getProviderSelectionConfig("bogus-provider")).toBe(null);
});

// Guard: the provider list is intentionally closed. CSP-specific wrappers
// (Bedrock, Vertex, Azure OpenAI, etc.) are already reachable through the
// "compatible-endpoint" or "compatible-anthropic-endpoint" options.
// Adding a new first-class provider key requires explicit approval.
it("does not grow beyond the approved provider set", () => {
const APPROVED_PROVIDERS = [
"nvidia-prod",
Expand All @@ -147,15 +122,9 @@ describe("inference selection config", () => {
"vllm-local",
"ollama-local",
];

// Every approved provider must still be recognised.
for (const key of APPROVED_PROVIDERS) {
expect(getProviderSelectionConfig(key)).not.toBe(null);
}

// Probe a broad set of plausible names; none outside the approved list
// should resolve. If this fails you are adding a new provider — use
// "compatible-endpoint" or "compatible-anthropic-endpoint" instead.
const CANDIDATES = [
"bedrock",
"vertex",
Expand All @@ -173,31 +142,25 @@ describe("inference selection config", () => {
"sambanova",
];
for (const key of CANDIDATES) {
expect(
getProviderSelectionConfig(key),
`"${key}" resolved as a provider — the provider list is closed. ` +
"CSP-specific endpoints should use the compatible-endpoint or " +
"compatible-anthropic-endpoint options instead. " +
"See https://github.com/NVIDIA/NemoClaw/pull/963 for rationale.",
).toBe(null);
expect(getProviderSelectionConfig(key)).toBe(null);
}
});

it("builds a qualified OpenClaw primary model for ollama-local", () => {
expect(getOpenClawPrimaryModel("ollama-local", "nemotron-3-nano:30b")).toBe(
`${MANAGED_PROVIDER_ID}/nemotron-3-nano:30b`,
it("falls back to provider defaults when model is omitted", () => {
expect(getProviderSelectionConfig("openai-api")?.model).toBe("gpt-5.4");
expect(getProviderSelectionConfig("anthropic-prod")?.model).toBe("claude-sonnet-4-6");
expect(getProviderSelectionConfig("gemini-api")?.model).toBe("gemini-2.5-flash");
expect(getProviderSelectionConfig("compatible-endpoint")?.model).toBe("custom-model");
expect(getProviderSelectionConfig("compatible-anthropic-endpoint")?.model).toBe(
"custom-anthropic-model",
);
expect(getProviderSelectionConfig("vllm-local")?.model).toBe("vllm-local");
});

it("falls back to provider defaults when model is omitted", () => {
expect(getProviderSelectionConfig("openai-api").model).toBe("gpt-5.4");
expect(getProviderSelectionConfig("anthropic-prod").model).toBe("claude-sonnet-4-6");
expect(getProviderSelectionConfig("gemini-api").model).toBe("gemini-2.5-flash");
expect(getProviderSelectionConfig("compatible-endpoint").model).toBe("custom-model");
expect(getProviderSelectionConfig("compatible-anthropic-endpoint").model).toBe(
"custom-anthropic-model",
it("builds a qualified OpenClaw primary model for ollama-local", () => {
expect(getOpenClawPrimaryModel("ollama-local", "nemotron-3-nano:30b")).toBe(
`${MANAGED_PROVIDER_ID}/nemotron-3-nano:30b`,
);
expect(getProviderSelectionConfig("vllm-local").model).toBe("vllm-local");
});

it("builds a default OpenClaw primary model for non-ollama providers", () => {
Expand Down Expand Up @@ -232,35 +195,18 @@ describe("parseGatewayInference", () => {
});

it("returns null when inference is not configured", () => {
const output = "Gateway inference:\n\n Not configured";
expect(parseGatewayInference(output)).toBeNull();
});

it("parses output with different provider/model combinations", () => {
const output = [
"Gateway inference:",
"",
" Provider: ollama-local",
" Model: qwen/qwen3.5-397b-a17b",
" Version: 1",
].join("\n");
expect(parseGatewayInference(output)).toEqual({
provider: "ollama-local",
model: "qwen/qwen3.5-397b-a17b",
});
expect(parseGatewayInference("Gateway inference:\n\n Not configured")).toBeNull();
});

it("handles output with only provider (no model line)", () => {
const output = "Gateway inference:\n\n Provider: nvidia-nim";
expect(parseGatewayInference(output)).toEqual({
expect(parseGatewayInference("Gateway inference:\n\n Provider: nvidia-nim")).toEqual({
provider: "nvidia-nim",
model: null,
});
});

it("handles output with only model (no provider line)", () => {
const output = "Gateway inference:\n\n Model: some/model";
expect(parseGatewayInference(output)).toEqual({
expect(parseGatewayInference("Gateway inference:\n\n Model: some/model")).toEqual({
provider: null,
model: "some/model",
});
Expand Down
Loading
Loading