Skip to content
Closed
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
47 changes: 43 additions & 4 deletions .jensenclaw/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,34 @@ const PORT = parseInt(process.env.JENSENCLAW_PORT || "18789", 10);
const API_KEY = process.env.NVIDIA_API_KEY;
const API_BASE = process.env.INFERENCE_URL || "https://integrate.api.nvidia.com/v1";
const MODEL = process.env.INFERENCE_MODEL || "nvidia/nemotron-3-super-120b-a12b";

// ── Curated model config (mirrors nemoclaw/src/proxy/models.ts) ──
const CURATED_MODELS = {
"moonshotai/kimi-k2.5": {
prefixedId: "private/openshell/moonshotai/kimi-k2.5",
extraBody: { chat_template_kwargs: { thinking: true } },
},
"minimaxai/minimax-m2.5": {
prefixedId: "private/openshell/minimaxai/minimax-m2.5",
extraBody: {},
},
"z-ai/glm5": {
prefixedId: "private/openshell/z-ai/glm5",
extraBody: { chat_template_kwargs: { enable_thinking: true } },
},
"nvidia/nemotron-3-super": {
prefixedId: "private/openshell/nvidia/nemotron-3-super-120b-a12b",
extraBody: { chat_template_kwargs: { enable_thinking: true, force_nonempty_content: true } },
},
"openai/gpt-oss-120b": {
prefixedId: "private/openshell/openai/gpt-oss-120b",
extraBody: { reasoning_effort: "high" },
},
};
const PROXY_HEADERS = {
"NVCF-POLL-SECONDS": "1800",
"X-BILLING-INVOKE-ORIGIN": "openshell",
};
const SANDBOX = process.env.SANDBOX_NAME || (() => {
// Read default sandbox from nemoclaw registry
try {
Expand Down Expand Up @@ -137,12 +165,21 @@ function runAgentInSandbox(message, sessionId) {
}

function proxyInference(messages, res) {
const body = JSON.stringify({
model: MODEL,
const curated = CURATED_MODELS[MODEL];

const bodyObj = {
model: curated ? curated.prefixedId : MODEL,
messages: [{ role: "system", content: SYSTEM_PROMPT }, ...messages],
max_tokens: 1024,
stream: true,
});
temperature: 1.0,
top_p: 0.95,
};

if (curated) {
Object.assign(bodyObj, curated.extraBody);
}

const body = JSON.stringify(bodyObj);

const url = new URL(`${API_BASE}/chat/completions`);
const options = {
Expand All @@ -152,8 +189,10 @@ function proxyInference(messages, res) {
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(body),
Authorization: `Bearer ${API_KEY}`,
Accept: "text/event-stream",
...PROXY_HEADERS,
},
};

Expand Down
8 changes: 8 additions & 0 deletions nemoclaw-blueprint/blueprint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ profiles:
- ncp
- nim-local
- vllm
- curated

description: |
NemoClaw blueprint: orchestrates OpenClaw sandbox creation, migration,
Expand Down Expand Up @@ -54,6 +55,13 @@ components:
credential_env: "OPENAI_API_KEY"
credential_default: "dummy"

curated:
provider_type: "nvidia"
provider_name: "nvidia-curated"
endpoint: "http://127.0.0.1:18990/v1"
model: "nvidia/nemotron-3-super-120b-a12b"
credential_env: "NVIDIA_API_KEY"

policy:
base: "sandboxes/openclaw/policy.yaml"
additions:
Expand Down
5 changes: 5 additions & 0 deletions nemoclaw/openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
"type": "string",
"description": "Default inference provider type (nvidia, vllm, openai-compatible)",
"default": "nvidia"
},
"proxyPort": {
"type": "number",
"description": "Local port for the policy-proxy (header/body injection for curated models)",
"default": 18990
}
},
"additionalProperties": false
Expand Down
15 changes: 14 additions & 1 deletion nemoclaw/src/commands/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "../onboard/config.js";
import { promptInput, promptConfirm, promptSelect } from "../onboard/prompt.js";
import { validateApiKey, maskApiKey } from "../onboard/validate.js";
import { getAllCuratedModels } from "../proxy/models.js";

export interface OnboardOptions {
apiKey?: string;
Expand All @@ -31,6 +32,7 @@ const DEFAULT_MODELS = [
{ id: "nvidia/llama-3.1-nemotron-ultra-253b-v1", label: "Nemotron Ultra 253B" },
{ id: "nvidia/llama-3.3-nemotron-super-49b-v1.5", label: "Nemotron Super 49B v1.5" },
{ id: "nvidia/nemotron-3-nano-30b-a3b", label: "Nemotron 3 Nano 30B" },
...getAllCuratedModels().map((m) => ({ id: m.id, label: `${m.label} (curated)` })),
];

function resolveProfile(endpointType: EndpointType): string {
Expand Down Expand Up @@ -281,6 +283,13 @@ export async function cliOnboard(opts: OnboardOptions): Promise<void> {
logger.info("Applying configuration...");

// 7a: Create/update provider
// For "build" endpoints, route through the local policy-proxy so that
// curated-model header/body injection happens transparently.
const proxyPort = opts.pluginConfig.proxyPort;
const providerBaseUrl = endpointType === "build"
? `http://127.0.0.1:${String(proxyPort)}/v1`
: endpointUrl;

try {
const result = execOpenShell([
"provider",
Expand All @@ -292,7 +301,7 @@ export async function cliOnboard(opts: OnboardOptions): Promise<void> {
"--credential",
`${credentialEnv}=${apiKey}`,
"--config",
`OPENAI_BASE_URL=${endpointUrl}`,
`OPENAI_BASE_URL=${providerBaseUrl}`,
]);
if (result.includes("AlreadyExists")) {
logger.info(`Provider '${providerName}' already exists, reusing.`);
Expand All @@ -309,6 +318,10 @@ export async function cliOnboard(opts: OnboardOptions): Promise<void> {
}
}

if (endpointType === "build") {
logger.info(`Policy-proxy active on port ${String(proxyPort)} (curated model support)`);
}

// 7b: Set inference route
try {
execOpenShell(["inference", "set", "--provider", providerName, "--model", model]);
Expand Down
46 changes: 46 additions & 0 deletions nemoclaw/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
*/

import type { Command } from "commander";
import type http from "node:http";
import { registerCliCommands } from "./cli.js";
import { handleSlashCommand } from "./commands/slash.js";
import { loadOnboardConfig } from "./onboard/config.js";
import { getAllCuratedModels } from "./proxy/models.js";
import { startProxyServer } from "./proxy/server.js";

// ---------------------------------------------------------------------------
// OpenClaw Plugin SDK compatible types (mirrors openclaw/plugin-sdk)
Expand Down Expand Up @@ -141,13 +144,15 @@ export interface NemoClawConfig {
blueprintRegistry: string;
sandboxName: string;
inferenceProvider: string;
proxyPort: number;
}

const DEFAULT_PLUGIN_CONFIG: NemoClawConfig = {
blueprintVersion: "latest",
blueprintRegistry: "ghcr.io/nvidia/nemoclaw-blueprint",
sandboxName: "openclaw",
inferenceProvider: "nvidia",
proxyPort: 18990,
};

export function getPluginConfig(api: OpenClawPluginApi): NemoClawConfig {
Expand All @@ -169,6 +174,10 @@ export function getPluginConfig(api: OpenClawPluginApi): NemoClawConfig {
typeof raw["inferenceProvider"] === "string"
? raw["inferenceProvider"]
: DEFAULT_PLUGIN_CONFIG.inferenceProvider,
proxyPort:
typeof raw["proxyPort"] === "number"
? (raw["proxyPort"] as number)
: DEFAULT_PLUGIN_CONFIG.proxyPort,
};
}

Expand All @@ -177,6 +186,8 @@ export function getPluginConfig(api: OpenClawPluginApi): NemoClawConfig {
// ---------------------------------------------------------------------------

export default function register(api: OpenClawPluginApi): void {
const pluginConfig = getPluginConfig(api);

// 1. Register /nemoclaw slash command (chat interface)
api.registerCommand({
name: "nemoclaw",
Expand All @@ -200,6 +211,14 @@ export default function register(api: OpenClawPluginApi): void {
? `NVIDIA NIM (${onboardCfg.endpointType}${onboardCfg.ncpPartner ? ` - ${onboardCfg.ncpPartner}` : ""})`
: "NVIDIA NIM (build.nvidia.com)";

// Build model catalog: existing models + curated models from proxy config
const curatedEntries = getAllCuratedModels().map((m) => ({
id: m.id,
label: m.label,
contextWindow: m.contextWindow,
maxOutput: m.maxOutput,
}));

api.registerProvider({
id: "nvidia-nim",
label: providerLabel,
Expand Down Expand Up @@ -232,6 +251,7 @@ export default function register(api: OpenClawPluginApi): void {
contextWindow: 131072,
maxOutput: 4096,
},
...curatedEntries,
],
},
auth: [
Expand All @@ -244,15 +264,41 @@ export default function register(api: OpenClawPluginApi): void {
],
});

// 4. Register policy-proxy service (header/body injection for curated models)
let proxyServerRef: http.Server | null = null;

api.registerService({
id: "policy-proxy",
start: (ctx) => {
const apiKey = process.env[providerCredentialEnv] ?? "";
const upstreamUrl = onboardCfg?.endpointUrl ?? "https://integrate.api.nvidia.com/v1";

proxyServerRef = startProxyServer({
port: pluginConfig.proxyPort,
upstreamUrl,
apiKey,
logger: ctx.logger,
});
},
stop: () => {
if (proxyServerRef) {
proxyServerRef.close();
proxyServerRef = null;
}
},
});

const bannerEndpoint = onboardCfg?.endpointType ?? "build.nvidia.com";
const bannerModel = onboardCfg?.model ?? "nvidia/nemotron-3-super-120b-a12b";
const proxyUrl = `http://127.0.0.1:${String(pluginConfig.proxyPort)}`;

api.logger.info("");
api.logger.info(" ┌─────────────────────────────────────────────────────┐");
api.logger.info(" │ NemoClaw registered │");
api.logger.info(" │ │");
api.logger.info(` │ Endpoint: ${bannerEndpoint.padEnd(40)}│`);
api.logger.info(` │ Model: ${bannerModel.padEnd(40)}│`);
api.logger.info(` │ Proxy: ${proxyUrl.padEnd(40)}│`);
api.logger.info(" │ Commands: openclaw nemoclaw <command> │");
api.logger.info(" └─────────────────────────────────────────────────────┘");
api.logger.info("");
Expand Down
94 changes: 94 additions & 0 deletions nemoclaw/src/proxy/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

export interface CuratedModel {
id: string;
prefixedId: string;
label: string;
extraBody: Record<string, unknown>;
aliases: string[];
contextWindow: number;
maxOutput: number;
}

export const DEFAULT_TEMPERATURE = 1.0;
export const DEFAULT_TOP_P = 0.95;

export const PROXY_HEADERS: Record<string, string> = {
"NVCF-POLL-SECONDS": "1800",
"X-BILLING-INVOKE-ORIGIN": "openshell",
};

const MODEL_PREFIX = "private/openshell";

function prefixed(id: string): string {
return `${MODEL_PREFIX}/${id}`;
}

const CURATED_MODELS_LIST: CuratedModel[] = [
{
id: "moonshotai/kimi-k2.5",
prefixedId: prefixed("moonshotai/kimi-k2.5"),
label: "Kimi K2.5",
extraBody: { chat_template_kwargs: { thinking: true } },
aliases: ["curated-nvidia-endpoints/moonshotai/kimi-k2.5"],
contextWindow: 131072,
maxOutput: 8192,
},
{
id: "minimaxai/minimax-m2.5",
prefixedId: prefixed("minimaxai/minimax-m2.5"),
label: "MiniMax M2.5",
extraBody: {},
aliases: ["curated-nvidia-endpoints/minimaxai/minimax-m2.5"],
contextWindow: 131072,
maxOutput: 8192,
},
{
id: "z-ai/glm5",
prefixedId: prefixed("z-ai/glm5"),
label: "GLM 5",
extraBody: { chat_template_kwargs: { enable_thinking: true } },
aliases: ["curated-nvidia-endpoints/z-ai/glm5"],
contextWindow: 131072,
maxOutput: 8192,
},
{
id: "nvidia/nemotron-3-super-120b-a12b",
prefixedId: prefixed("nvidia/nemotron-3-super-120b-a12b"),
label: "Nemotron 3 Super",
extraBody: {
chat_template_kwargs: {
enable_thinking: true,
force_nonempty_content: true,
},
},
aliases: ["curated-nvidia-endpoints/nvidia/nemotron-3-super-120b-a12b"],
contextWindow: 131072,
maxOutput: 8192,
},
{
id: "openai/gpt-oss-120b",
prefixedId: prefixed("openai/gpt-oss-120b"),
label: "GPT-OSS 120B",
extraBody: { reasoning_effort: "high" },
aliases: ["curated-nvidia-endpoints/openai/gpt-oss-120b"],
contextWindow: 131072,
maxOutput: 8192,
},
];

export const CURATED_MODELS: ReadonlyMap<string, CuratedModel> = (() => {
const map = new Map<string, CuratedModel>();
for (const m of CURATED_MODELS_LIST) {
map.set(m.id, m);
for (const alias of m.aliases) {
map.set(alias, m);
}
}
return map;
})();

export function getAllCuratedModels(): readonly CuratedModel[] {
return CURATED_MODELS_LIST;
}
34 changes: 34 additions & 0 deletions nemoclaw/src/proxy/retry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

interface Choice {
finish_reason?: string;
}

interface CompletionResponse {
choices?: Choice[];
}

/**
* Check whether a non-streaming completion response was truncated due to
* token length, indicating the client should retry.
*/
export function shouldRetry(response: Record<string, unknown>): boolean {
const parsed = response as unknown as CompletionResponse;
if (!Array.isArray(parsed.choices)) return false;
return parsed.choices.some((c) => c.finish_reason === "length");
}

/**
* Scan an SSE data line for `finish_reason: "length"`.
* Returns true when the chunk signals truncation.
*/
export function shouldRetryStreamChunk(dataLine: string): boolean {
try {
const parsed = JSON.parse(dataLine) as CompletionResponse;
if (!Array.isArray(parsed.choices)) return false;
return parsed.choices.some((c) => c.finish_reason === "length");
} catch {
return false;
}
}
Loading