Skip to content
4 changes: 2 additions & 2 deletions docs/inference/inference-options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ The managed install/start vLLM entry appears by default on DGX Spark and DGX Sta

| Option | Description | Curated models |
|--------|-------------|----------------|
| NVIDIA Endpoints | Routes to models hosted on [build.nvidia.com](https://build.nvidia.com). You can also enter any model ID from the catalog. Set `NVIDIA_INFERENCE_API_KEY`. | Nemotron 3 Super 120B, Nemotron 3 Ultra 550B, GLM-5.1, MiniMax M2.7, GPT-OSS 120B, DeepSeek V4 Pro |
| NVIDIA Endpoints | Routes to models hosted on [build.nvidia.com](https://build.nvidia.com). You can also enter any model ID from the catalog. Set `NVIDIA_INFERENCE_API_KEY`. | Nemotron 3 Super 120B, Nemotron 3 Ultra 550B, GLM-5.1, MiniMax M2.7, Kimi K2.7 Code when the authenticated `/v1/models` catalog lists it, GPT-OSS 120B, DeepSeek V4 Pro |
| OpenAI | Routes to the OpenAI API. Set `OPENAI_API_KEY`. | `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.4-pro-2026-03-05` |
| Other OpenAI-compatible endpoint | Routes to any server that implements `/v1/chat/completions`. NemoClaw uses `/v1/chat/completions` at runtime by default; set `NEMOCLAW_PREFERRED_API=openai-responses` to allow `/v1/responses` for proxies that implement it, such as some llama.cpp builds. The wizard prompts for a base URL and model name. Works with OpenRouter, LocalAI, llama.cpp, or any compatible proxy. When you enable Telegram messaging, onboarding also runs a bounded sandbox-side smoke check through `https://inference.local/v1/chat/completions`. Set `COMPATIBLE_API_KEY`. | You provide the model name. |
| Anthropic | Routes to the Anthropic Messages API. Set `ANTHROPIC_API_KEY`. | `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-6` |
Expand Down Expand Up @@ -192,7 +192,7 @@ Other provider credentials, such as `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMI
| Google Gemini | Validates through Gemini's OpenAI-compatible chat-completions path only; NemoClaw skips the `/v1/responses` probe because Gemini does not support the Responses API. |
| Other OpenAI-compatible endpoint | Tries `/v1/responses` first with a tool-calling probe; falls back to `/v1/chat/completions`. Selected runtime API defaults to `/v1/chat/completions`; set `NEMOCLAW_PREFERRED_API=openai-responses` to allow `/v1/responses` at runtime when validation succeeds. |
| Anthropic-compatible | Tries `/v1/messages`. |
| NVIDIA Endpoints (manual model entry) | Validates the model name against the catalog API. |
| NVIDIA Endpoints (catalog-gated curated models and manual model entry) | Validates the model name against the catalog API before accepting models whose availability is still rolling out. |
| Compatible endpoints | Sends a real inference request because many proxies do not expose a `/models` endpoint. For OpenAI-compatible endpoints, the probe tries `/v1/responses` first then falls back to `/v1/chat/completions`; the selected runtime API defaults to `/v1/chat/completions`. Set `NEMOCLAW_PREFERRED_API=openai-responses` to allow `/v1/responses` at runtime when validation succeeds. |
| Local NVIDIA NIM | Validates through `/v1/chat/completions` only; NemoClaw skips the `/v1/responses` probe (same as NVIDIA Endpoints). |

Expand Down
2 changes: 1 addition & 1 deletion nemoclaw-blueprint/model-specific-setup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Manifests follow `schema.json`:
- `match`: model/provider route predicates.
- `effects`: declarative, agent-scoped effects.

The first OpenClaw entry is `openclaw/kimi-k2.6-managed-inference.json`. It preserves the Kimi K2.6 managed `inference.local` compatibility behavior from PR #3046.
The first OpenClaw entry is `openclaw/kimi-k2.6-managed-inference.json`. It preserves the Kimi K2.6 managed `inference.local` compatibility behavior from PR #3046. Later Kimi variants, including Kimi K2.7 Code, must stay out of this manifest until captured or source-confirmed managed-route output proves they emit the same OpenClaw-incompatible reasoning/tool-call shape and documents the removal condition.

## Contributor Guidance

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "../schema.json",
"id": "kimi-k2.6-managed-inference",
"agent": "openclaw",
"description": "Preserves OpenClaw request, tool-call, and reasoning-output compatibility for moonshotai/kimi-k2.6 through NemoClaw managed inference.local chat completions.",
"description": "Preserves OpenClaw request, tool-call, and reasoning-output compatibility for Kimi K2.6 through NemoClaw managed inference.local chat completions. Add later Kimi variants only after captured or source-confirmed managed-route output proves they need the same shim.",
"match": {
"modelIds": ["moonshotai/kimi-k2.6"],
"providerKey": "inference",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ function normalizeBaseUrl(value) {
return String(value || "").trim().replace(/\/+$/, "");
}

const KIMI_K26_MODEL_ID = "moonshotai/kimi-k2.6";
const MANAGED_KIMI_K26_MODEL_REF = `inference/${KIMI_K26_MODEL_ID}`;
const KIMI_MODEL_IDS = new Set(["moonshotai/kimi-k2.6"]);

function isKimiModelId(value) {
const modelId = normalize(value);
return modelId === KIMI_K26_MODEL_ID || modelId === MANAGED_KIMI_K26_MODEL_REF;
const unqualifiedModelId = modelId.startsWith("inference/") ? modelId.slice(10) : modelId;
return KIMI_MODEL_IDS.has(unqualifiedModelId);
}

function isManagedKimi(ctx) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ The managed install/start vLLM entry appears by default on DGX Spark and DGX Sta

| Option | Description | Curated models |
|--------|-------------|----------------|
| NVIDIA Endpoints | Routes to models hosted on [build.nvidia.com](https://build.nvidia.com). You can also enter any model ID from the catalog. Set `NVIDIA_API_KEY`. | Nemotron 3 Super 120B, Nemotron 3 Ultra 550B, GLM-5.1, MiniMax M2.7, GPT-OSS 120B, DeepSeek V4 Pro |
| NVIDIA Endpoints | Routes to models hosted on [build.nvidia.com](https://build.nvidia.com). You can also enter any model ID from the catalog. Set `NVIDIA_API_KEY`. | Nemotron 3 Super 120B, Nemotron 3 Ultra 550B, GLM-5.1, MiniMax M2.7, Kimi K2.7 Code when the authenticated `/v1/models` catalog lists it, GPT-OSS 120B, DeepSeek V4 Pro |
| OpenAI | Routes to the OpenAI API. Set `OPENAI_API_KEY`. | `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.4-pro-2026-03-05` |
| Other OpenAI-compatible endpoint | Routes to any server that implements `/v1/chat/completions`. NemoClaw uses `/v1/chat/completions` at runtime by default; set `NEMOCLAW_PREFERRED_API=openai-responses` to allow `/v1/responses` for proxies that implement it, such as some llama.cpp builds. The wizard prompts for a base URL and model name. Works with OpenRouter, LocalAI, llama.cpp, or any compatible proxy. When you enable Telegram messaging, onboarding also runs a bounded sandbox-side smoke check through `https://inference.local/v1/chat/completions`. Set `COMPATIBLE_API_KEY`. | You provide the model name. |
| Anthropic | Routes to the Anthropic Messages API. Set `ANTHROPIC_API_KEY`. | `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-6` |
Expand Down Expand Up @@ -181,7 +181,7 @@ Other provider credentials, such as `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMI
| Google Gemini | Validates through Gemini's OpenAI-compatible chat-completions path only; NemoClaw skips the `/v1/responses` probe because Gemini does not support the Responses API. |
| Other OpenAI-compatible endpoint | Tries `/v1/responses` first with a tool-calling probe; falls back to `/v1/chat/completions`. Selected runtime API defaults to `/v1/chat/completions`; set `NEMOCLAW_PREFERRED_API=openai-responses` to allow `/v1/responses` at runtime when validation succeeds. |
| Anthropic-compatible | Tries `/v1/messages`. |
| NVIDIA Endpoints (manual model entry) | Validates the model name against the catalog API. |
| NVIDIA Endpoints (catalog-gated curated models and manual model entry) | Validates the model name against the catalog API before accepting models whose availability is still rolling out. |
| Compatible endpoints | Sends a real inference request because many proxies do not expose a `/models` endpoint. For OpenAI-compatible endpoints, the probe tries `/v1/responses` first then falls back to `/v1/chat/completions`; the selected runtime API defaults to `/v1/chat/completions`. Set `NEMOCLAW_PREFERRED_API=openai-responses` to allow `/v1/responses` at runtime when validation succeeds. |
| Local NVIDIA NIM | Validates through `/v1/chat/completions` only; NemoClaw skips the `/v1/responses` probe (same as NVIDIA Endpoints). |

Expand Down
63 changes: 63 additions & 0 deletions src/lib/adapters/http/curl-auth-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import fs from "node:fs";
import os from "node:os";
import path from "node:path";

import { afterEach, describe, expect, it } from "vitest";

import {
cleanupAuthCurlConfig,
createAuthCurlConfig,
} from "../../../../dist/lib/adapters/http/curl-auth-config";

const cleanupPaths: string[] = [];

afterEach(() => {
for (const dir of cleanupPaths.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});

describe("auth curl config helpers", () => {
it("cleans up auth config directories created below the temp root", () => {
const configPath = createAuthCurlConfig("Authorization: Bearer nvapi-x", "nemoclaw-auth-test");
const dir = path.dirname(configPath);

expect(fs.existsSync(configPath)).toBe(true);

cleanupAuthCurlConfig(configPath, "nemoclaw-auth-test");

expect(fs.existsSync(dir)).toBe(false);
});

it("does not remove a matching directory outside the temp root", () => {
const parentDir = fs.mkdtempSync(path.join(process.cwd(), ".tmp-nemoclaw-auth-parent-"));
const outsideDir = path.join(parentDir, "nemoclaw-auth-test-stale");
cleanupPaths.push(parentDir);
fs.mkdirSync(outsideDir);
const configPath = path.join(outsideDir, "auth.conf");
fs.writeFileSync(configPath, 'header = "Authorization: Bearer nvapi-x"\n', {
mode: 0o600,
encoding: "utf8",
});

cleanupAuthCurlConfig(configPath, "nemoclaw-auth-test");

expect(fs.existsSync(outsideDir)).toBe(true);
expect(fs.existsSync(configPath)).toBe(true);
});

it("rejects path-like temp prefixes", () => {
expect(() => createAuthCurlConfig("Authorization: Bearer nvapi-x", "nested/prefix")).toThrow(
/Invalid temp file prefix/,
);
expect(() =>
cleanupAuthCurlConfig(
path.join(os.tmpdir(), "nested", "prefix-stale", "auth.conf"),
"nested/prefix",
),
).toThrow(/Invalid temp file prefix/);
});
});
62 changes: 62 additions & 0 deletions src/lib/adapters/http/curl-auth-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import fs from "node:fs";
import os from "node:os";
import path from "node:path";

const DEFAULT_CURL_AUTH_CONFIG_PREFIX = "nemoclaw-auth-curl";

function validateTempPrefix(prefix: string): string {
if (
prefix.length === 0 ||
prefix !== path.basename(prefix) ||
prefix.includes(path.posix.sep) ||
prefix.includes(path.win32.sep)
) {
throw new Error(`Invalid temp file prefix: ${prefix}`);
}
return prefix;
}

function quoteCurlConfigValue(value: string): string {
return value
.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.replace(/[\r\n]+/g, " ");
}

export function createAuthCurlConfig(
headerValue: string,
prefix = DEFAULT_CURL_AUTH_CONFIG_PREFIX,
): string {
const safePrefix = validateTempPrefix(prefix);
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `${safePrefix}-`));
try {
fs.chmodSync(dir, 0o700);
const configPath = path.join(dir, "auth.conf");
fs.writeFileSync(configPath, `header = "${quoteCurlConfigValue(headerValue)}"\n`, {
mode: 0o600,
encoding: "utf8",
});
return configPath;
} catch (error) {
fs.rmSync(dir, { recursive: true, force: true });
throw error;
}
}

export function cleanupAuthCurlConfig(
configPath: string,
prefix = DEFAULT_CURL_AUTH_CONFIG_PREFIX,
): void {
const safePrefix = validateTempPrefix(prefix);
const tempRoot = path.resolve(os.tmpdir());
const parentDir = path.resolve(path.dirname(configPath));
const relativeParent = path.relative(tempRoot, parentDir);
const isInsideTempRoot =
relativeParent !== "" && !relativeParent.startsWith("..") && !path.isAbsolute(relativeParent);
if (isInsideTempRoot && path.basename(parentDir).startsWith(`${safePrefix}-`)) {
fs.rmSync(parentDir, { recursive: true, force: true });
}
}
20 changes: 19 additions & 1 deletion src/lib/inference/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { describe, expect, it } from "vitest";
// Import from compiled dist/ for correct coverage attribution.
import {
CLOUD_MODEL_OPTIONS,
DEFAULT_CLOUD_MODEL,
DEFAULT_HERMES_PROVIDER_MODEL,
DEFAULT_OLLAMA_MODEL,
DEFAULT_ROUTE_CREDENTIAL_ENV,
Expand All @@ -31,10 +32,15 @@ describe("inference selection config", () => {
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning",
"z-ai/glm-5.1",
"minimaxai/minimax-m2.7",
"moonshotai/kimi-k2.6",
"moonshotai/kimi-k2.7-code",
"openai/gpt-oss-120b",
"deepseek-ai/deepseek-v4-pro",
]);
expect(CLOUD_MODEL_OPTIONS[5]).toMatchObject({
id: "moonshotai/kimi-k2.7-code",
requiresCatalogValidation: true,
});
expect(DEFAULT_CLOUD_MODEL).toBe("nvidia/nemotron-3-super-120b-a12b");
});

it("aligns Hermes Provider defaults with the Hermes Agent Nous catalog", () => {
Expand Down Expand Up @@ -272,6 +278,18 @@ describe("getSandboxInferenceConfig", () => {
});
});

it("routes Kimi K2.7 Code through managed inference without the K2.6 compat shim", () => {
expect(
getSandboxInferenceConfig("moonshotai/kimi-k2.7-code", "nvidia-prod", "openai-completions"),
).toEqual({
providerKey: MANAGED_PROVIDER_ID,
primaryModelRef: `${MANAGED_PROVIDER_ID}/moonshotai/kimi-k2.7-code`,
inferenceBaseUrl: INFERENCE_ROUTE_URL,
inferenceApi: "openai-completions",
inferenceCompat: null,
});
});

it("maps OpenAI-compatible endpoints to the managed inference provider", () => {
expect(
getSandboxInferenceConfig("deepseek-ai/DeepSeek-V4-Flash", "compatible-endpoint"),
Expand Down
6 changes: 5 additions & 1 deletion src/lib/inference/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ export const CLOUD_MODEL_OPTIONS = [
{ id: "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning", label: "Nemotron 3 Nano Omni 30B" },
{ id: "z-ai/glm-5.1", label: "GLM-5" },
{ id: "minimaxai/minimax-m2.7", label: "MiniMax M2.7" },
{ id: "moonshotai/kimi-k2.6", label: "Kimi K2.6" },
{
id: "moonshotai/kimi-k2.7-code",
label: "Kimi K2.7 Code",
requiresCatalogValidation: true,
},
{ id: "openai/gpt-oss-120b", label: "GPT-OSS 120B" },
{ id: "deepseek-ai/deepseek-v4-pro", label: "DeepSeek V4 Pro" },
];
Expand Down
Loading
Loading