From c7947914114f2afce6ea4bdcd729d15d92556506 Mon Sep 17 00:00:00 2001 From: Shivay Lamba Date: Tue, 27 Jan 2026 10:10:52 +0000 Subject: [PATCH] Add Nebius models support --- docs/concepts/model-providers.md | 3 + docs/gateway/configuration.md | 39 +++++++++++ docs/providers/index.md | 1 + docs/providers/nebius.md | 47 +++++++++++++ docs/testing.md | 3 +- src/agents/model-auth.ts | 1 + src/agents/models-config.providers.ts | 48 +++++++++++++ .../reply/directive-handling.model-picker.ts | 1 + src/cli/program/register.onboard.ts | 1 + src/commands/auth-choice-options.ts | 12 ++++ .../auth-choice.apply.api-providers.ts | 70 +++++++++++++++++++ src/commands/models/list.status-command.ts | 1 + src/commands/onboard-auth.config-core.ts | 49 +++++++++++++ src/commands/onboard-auth.credentials.ts | 14 ++++ src/commands/onboard-auth.ts | 4 ++ .../local/auth-choice.ts | 22 ++++++ src/commands/onboard-types.ts | 2 + 17 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 docs/providers/nebius.md diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md index 46dc4f749..3e5d6fad4 100644 --- a/docs/concepts/model-providers.md +++ b/docs/concepts/model-providers.md @@ -118,6 +118,9 @@ Clawdbot ships with the pi‑ai catalog. These providers require **no** - OpenAI-compatible base URL: `https://api.cerebras.ai/v1`. - Mistral: `mistral` (`MISTRAL_API_KEY`) - GitHub Copilot: `github-copilot` (`COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN`) +- Nebius: `nebius` (`NEBIUS_API_KEY`) + - GLM models on Nebius use ids `zai-org/GLM-4.7-FP8` and `zai-org/GLM-4.5` + - OpenAI-compatible base URL: `https://api.tokenfactory.nebius.com/v1/`. ## Providers via `models.providers` (custom/base URL) diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index cea4d786d..54dd23e4b 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -2608,6 +2608,45 @@ Notes: - Override the agent config root with `CLAWDBOT_AGENT_DIR` (or `PI_CODING_AGENT_DIR`) if you want `models.json` stored elsewhere (default: `~/.clawdbot/agents/main/agent`). + +### Nebius (GLM 7 / GLM 5) + +Use Nebius via their OpenAI-compatible endpoint: + +```json5 +{ + env: { + NEBIUS_API_KEY: "" }, + + agents: { + defaults: { + model: { + primary: "zai-org/GLM-4.7-FP8", + fallbacks: ["zai-org/GLM-4.5"] + }, + models: { + "zai-org/GLM-4.7-FP8": { alias: "GLM 7 (Nebius)" }, + "zai-org/GLM-4.5": { alias: "GLM 5 (Nebius)" } + } + } + }, + + models: { + mode: "merge", + providers: { + nebius: { + baseUrl: "https://api.tokenfactory.nebius.com/v1", + apiKey: "${NEBIUS_API_KEY}", + api: "openai-completions", + models: [ + { id: "zai-org/GLM-4.7-FP8", name: "GLM 7 (Nebius)" }, + { id: "zai-org/GLM-4.5", name: "GLM 5 (Nebius)" } + ] + } + } + } +} + ### `session` Controls session scoping, reset policy, reset triggers, and where the session store is written. diff --git a/docs/providers/index.md b/docs/providers/index.md index b4779d201..f39da3b7f 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -46,6 +46,7 @@ See [Venice AI](/providers/venice). - [MiniMax](/providers/minimax) - [Venius (Venice AI, privacy-focused)](/providers/venice) - [Ollama (local models)](/providers/ollama) +- [Nebius](/providers/nebius) ## Transcription providers diff --git a/docs/providers/nebius.md b/docs/providers/nebius.md new file mode 100644 index 000000000..76ecaa77b --- /dev/null +++ b/docs/providers/nebius.md @@ -0,0 +1,47 @@ +--- +summary: "Use Nebius OpenAI-compatible inference for GLM and other frontier open models" +read_when: + - You want to use Nebius inference + - You need OpenAI-compatible access to GLM models +--- +# Nebius + +Nebius provides **OpenAI-compatible inference** for frontier and open-source models, including **GLM**, via the Nebius TokenFactory API. This allows seamless drop-in usage with existing OpenAI-style clients and tooling. + +## CLI setup + +```bash +clawdbot onboard --auth-choice nebius-api-key +# or non-interactive +clawdbot onboard --nebius-api-key "$NEBIUS_API_KEY" + +``` + +## Config snippet + +```json5 +{ + env: { NEBIUS_API_KEY: "sk-..." }, + agents: { + defaults: { + model: { + primary: "nebius/zai-glm-7", + fallbacks: ["nebius/zai-glm-5"] + } + } + } +} +``` + +## Available models + +- `zai-org/GLM-4.7-FP8` – GLM 7 +- `zai-org/GLM-4.5` – GLM 5 + +## Notes + +- Base URL: https://api.tokenfactory.nebius.com/v1 +- OpenAI-compatible Chat Completions API +- Model refs use nebius/ format +- Set NEBIUS_API_KEY in the environment or config +- Works with standard OpenAI SDKs (Python, JS, etc.) \ No newline at end of file diff --git a/docs/testing.md b/docs/testing.md index 4fac104da..af870e331 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -262,6 +262,7 @@ Optional additional coverage (nice to have): - Mistral: `mistral/`… (pick one “tools” capable model you have enabled) - Cerebras: `cerebras/`… (if you have access) - LM Studio: `lmstudio/`… (local; tool calling depends on API mode) +- Nebius: `nebius/` … (if you have access) ### Vision: image send (attachment → multimodal message) @@ -274,7 +275,7 @@ If you have keys enabled, we also support testing via: - OpenCode Zen: `opencode/...` (auth via `OPENCODE_API_KEY` / `OPENCODE_ZEN_API_KEY`) More providers you can include in the live matrix (if you have creds/config): -- Built-in: `openai`, `openai-codex`, `anthropic`, `google`, `google-vertex`, `google-antigravity`, `google-gemini-cli`, `zai`, `openrouter`, `opencode`, `xai`, `groq`, `cerebras`, `mistral`, `github-copilot` +- Built-in: `openai`, `openai-codex`, `anthropic`, `google`, `google-vertex`, `google-antigravity`, `google-gemini-cli`, `zai`, `openrouter`, `opencode`, `xai`, `groq`, `cerebras`, `nebius`, `mistral`, `github-copilot` - Via `models.providers` (custom endpoints): `minimax` (cloud/API), plus any OpenAI/Anthropic-compatible proxy (LM Studio, vLLM, LiteLLM, etc.) Tip: don’t try to hardcode “all models” in docs. The authoritative list is whatever `discoverModels(...)` returns on your machine + whatever keys are available. diff --git a/src/agents/model-auth.ts b/src/agents/model-auth.ts index 680d0f53c..98f245d92 100644 --- a/src/agents/model-auth.ts +++ b/src/agents/model-auth.ts @@ -285,6 +285,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null { venice: "VENICE_API_KEY", mistral: "MISTRAL_API_KEY", opencode: "OPENCODE_API_KEY", + nebius: "NEBIUS_API_KEY" }; const envVar = envMap[normalized]; if (!envVar) return null; diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 996f09dd0..3bc07de07 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -64,6 +64,17 @@ const QWEN_PORTAL_DEFAULT_COST = { cacheWrite: 0, }; +const NEBIUS_BASE_URL = "https://api.tokenfactory.nebius.com/v1"; +const NEBIUS_DEFAULT_CONTEXT_WINDOW = 128000; +const NEBIUS_DEFAULT_MAX_TOKENS = 8192; +const NEBIUS_DEFAULT_COST = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, +}; + + const OLLAMA_BASE_URL = "http://127.0.0.1:11434/v1"; const OLLAMA_API_BASE_URL = "http://127.0.0.1:11434"; const OLLAMA_DEFAULT_CONTEXT_WINDOW = 128000; @@ -359,6 +370,34 @@ async function buildOllamaProvider(): Promise { }; } +function buildNebiusProvider(): ProviderConfig { + return { + baseUrl: NEBIUS_BASE_URL, + api: "openai-completions", + models: [ + { + id: "zai-org/GLM-4.7-FP8", + name: "GLM 7", + reasoning: false, + input: ["text"], + cost: NEBIUS_DEFAULT_COST, + contextWindow: NEBIUS_DEFAULT_CONTEXT_WINDOW, + maxTokens: NEBIUS_DEFAULT_MAX_TOKENS, + }, + { + id: "zai-org/GLM-4.5", + name: "GLM 5", + reasoning: false, + input: ["text"], + cost: NEBIUS_DEFAULT_COST, + contextWindow: NEBIUS_DEFAULT_CONTEXT_WINDOW, + maxTokens: NEBIUS_DEFAULT_MAX_TOKENS, + }, + ], + }; +} + + export async function resolveImplicitProviders(params: { agentDir: string; }): Promise { @@ -418,6 +457,15 @@ export async function resolveImplicitProviders(params: { providers.ollama = { ...(await buildOllamaProvider()), apiKey: ollamaKey }; } + const nebiusKey = + resolveEnvApiKeyVarName("nebius") ?? + resolveApiKeyFromProfiles({ provider: "nebius", store: authStore }); + +if (nebiusKey) { + providers.nebius = { ...buildNebiusProvider(), apiKey: nebiusKey }; +} + + return providers; } diff --git a/src/auto-reply/reply/directive-handling.model-picker.ts b/src/auto-reply/reply/directive-handling.model-picker.ts index 0fd2af367..ca50167ed 100644 --- a/src/auto-reply/reply/directive-handling.model-picker.ts +++ b/src/auto-reply/reply/directive-handling.model-picker.ts @@ -25,6 +25,7 @@ const MODEL_PICK_PROVIDER_PREFERENCE = [ "mistral", "xai", "lmstudio", + "nebius" ] as const; const PROVIDER_RANK = new Map( diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index eac6a60df..50e6cf745 100644 --- a/src/cli/program/register.onboard.ts +++ b/src/cli/program/register.onboard.ts @@ -76,6 +76,7 @@ export function registerOnboardCommand(program: Command) { .option("--synthetic-api-key ", "Synthetic API key") .option("--venice-api-key ", "Venice API key") .option("--opencode-zen-api-key ", "OpenCode Zen API key") + .option("--nebius-api-key ", "Nebius API key") .option("--gateway-port ", "Gateway port") .option("--gateway-bind ", "Gateway bind: loopback|tailnet|lan|auto|custom") .option("--gateway-auth ", "Gateway auth: token|password") diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 6b49ff17b..7853e41e8 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -20,6 +20,7 @@ export type AuthChoiceGroupId = | "minimax" | "synthetic" | "venice" + | "nebius" | "qwen"; export type AuthChoiceGroup = { @@ -107,6 +108,12 @@ const AUTH_CHOICE_GROUP_DEFS: { hint: "API key", choices: ["zai-api-key"], }, + { + value: "nebius", + label: "Nebius", + hint: "Nebius Token Factory", + choices: ["nebius-api-key"], + }, { value: "opencode-zen", label: "OpenCode Zen", @@ -178,6 +185,11 @@ export function buildAuthChoiceOptions(params: { hint: "Claude, GPT, Gemini via opencode.ai/zen", }); options.push({ value: "minimax-api", label: "MiniMax M2.1" }); + options.push({ + value: "nebius-api-key", + label: "Nebius API key", + hint: "Nebius Token Factory)", + }); options.push({ value: "minimax-api-lightning", label: "MiniMax M2.1 Lightning", diff --git a/src/commands/auth-choice.apply.api-providers.ts b/src/commands/auth-choice.apply.api-providers.ts index 8be02008b..854f252fd 100644 --- a/src/commands/auth-choice.apply.api-providers.ts +++ b/src/commands/auth-choice.apply.api-providers.ts @@ -17,6 +17,8 @@ import { applyKimiCodeProviderConfig, applyMoonshotConfig, applyMoonshotProviderConfig, + applyNebiusConfig, + applyNebiusProviderConfig, applyOpencodeZenConfig, applyOpencodeZenProviderConfig, applyOpenrouterConfig, @@ -30,6 +32,7 @@ import { applyZaiConfig, KIMI_CODE_MODEL_REF, MOONSHOT_DEFAULT_MODEL_REF, + NEBIUS_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF, SYNTHETIC_DEFAULT_MODEL_REF, VENICE_DEFAULT_MODEL_REF, @@ -37,6 +40,7 @@ import { setGeminiApiKey, setKimiCodeApiKey, setMoonshotApiKey, + setNebiusApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, setSyntheticApiKey, @@ -83,6 +87,8 @@ export async function applyAuthChoiceApiProviders( authChoice = "synthetic-api-key"; } else if (params.opts.tokenProvider === "venice") { authChoice = "venice-api-key"; + } else if (params.opts.tokenProvider === "nebius") { + authChoice = "nebius-api-key"; } else if (params.opts.tokenProvider === "opencode") { authChoice = "opencode-zen"; } @@ -522,6 +528,70 @@ export async function applyAuthChoiceApiProviders( return { config: nextConfig, agentModelOverride }; } + if (authChoice === "nebius-api-key") { + let hasCredential = false; + + if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "nebius") { + await setNebiusApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir); + hasCredential = true; + } + + if (!hasCredential) { + await params.prompter.note( + [ + "Nebius provides OpenAI-compatible inference for frontier and open models.", + "Get your API key at: hhttps://tokenfactory.nebius.com/", + "Available models: zai-glm-7, zai-glm-5", + ].join("\n"), + "Nebius", + ); + } + + const envKey = resolveEnvApiKey("nebius"); + if (envKey) { + const useExisting = await params.prompter.confirm({ + message: `Use existing NEBIUS_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`, + initialValue: true, + }); + if (useExisting) { + await setNebiusApiKey(envKey.apiKey, params.agentDir); + hasCredential = true; + } + } + + if (!hasCredential) { + const key = await params.prompter.text({ + message: "Enter Nebius API key", + validate: validateApiKeyInput, + }); + await setNebiusApiKey(normalizeApiKeyInput(String(key)), params.agentDir); + } + + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "nebius:default", + provider: "nebius", + mode: "api_key", + }); + + { + const applied = await applyDefaultModelChoice({ + config: nextConfig, + setDefaultModel: params.setDefaultModel, + defaultModel: NEBIUS_DEFAULT_MODEL_REF, + applyDefaultConfig: applyNebiusConfig, + applyProviderConfig: applyNebiusProviderConfig, + noteDefault: NEBIUS_DEFAULT_MODEL_REF, + noteAgentModel, + prompter: params.prompter, + }); + nextConfig = applied.config; + agentModelOverride = applied.agentModelOverride ?? agentModelOverride; + } + + return { config: nextConfig, agentModelOverride }; +} + + if (authChoice === "opencode-zen") { let hasCredential = false; if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "opencode") { diff --git a/src/commands/models/list.status-command.ts b/src/commands/models/list.status-command.ts index fc29cc5d5..c21f3f930 100644 --- a/src/commands/models/list.status-command.ts +++ b/src/commands/models/list.status-command.ts @@ -134,6 +134,7 @@ export async function modelsStatusCommand( "zai", "mistral", "synthetic", + "nebius" ]; for (const provider of envProbeProviders) { if (resolveEnvApiKey(provider)) providersFromEnv.add(provider); diff --git a/src/commands/onboard-auth.config-core.ts b/src/commands/onboard-auth.config-core.ts index 0d3a8523a..30e2949a1 100644 --- a/src/commands/onboard-auth.config-core.ts +++ b/src/commands/onboard-auth.config-core.ts @@ -336,6 +336,55 @@ export function applySyntheticConfig(cfg: ClawdbotConfig): ClawdbotConfig { }; } +export function applyNebiusProviderConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const models = { ...cfg.agents?.defaults?.models }; + + models["nebius/zai-glm-7"] = { + ...models["nebius/zai-glm-7"], + alias: models["nebius/zai-glm-7"]?.alias ?? "GLM 7", + }; + + models["nebius/zai-glm-5"] = { + ...models["nebius/zai-glm-5"], + alias: models["nebius/zai-glm-5"]?.alias ?? "GLM 5", + }; + + return { + ...cfg, + agents: { + ...cfg.agents, + defaults: { + ...cfg.agents?.defaults, + models, + }, + }, + }; +} + +export function applyNebiusConfig(cfg: ClawdbotConfig): ClawdbotConfig { + const next = applyNebiusProviderConfig(cfg); + const existingModel = next.agents?.defaults?.model; + + return { + ...next, + agents: { + ...next.agents, + defaults: { + ...next.agents?.defaults, + model: { + ...(existingModel && "fallbacks" in (existingModel as Record) + ? { + fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks, + } + : undefined), + primary: "nebius/zai-glm-7", + }, + }, + }, + }; +} + + /** * Apply Venice provider configuration without changing the default model. * Registers Venice models and sets up the provider, but preserves existing model selection. diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index 0c7dff409..db88e6120 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -115,6 +115,7 @@ export async function setVeniceApiKey(key: string, agentDir?: string) { export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7"; export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto"; export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5"; +export const NEBIUS_DEFAULT_MODEL_REF = "nebius/meta-llama/Llama-3.3-70B-Instruct-fast" export async function setZaiApiKey(key: string, agentDir?: string) { // Write to resolved agent dir so gateway finds credentials on startup. @@ -164,3 +165,16 @@ export async function setOpencodeZenApiKey(key: string, agentDir?: string) { agentDir: resolveAuthAgentDir(agentDir), }); } + +export async function seNebiusApiKey(key: string, agentDir?: string) { + upsertAuthProfile({ + profileId: "nebius:default", + credential: { + type: "api_key", + provider: "nebius", + key, + }, + agentDir: resolveAuthAgentDir(agentDir), + }); +} + diff --git a/src/commands/onboard-auth.ts b/src/commands/onboard-auth.ts index b122d89cf..1cdebeb0c 100644 --- a/src/commands/onboard-auth.ts +++ b/src/commands/onboard-auth.ts @@ -9,6 +9,8 @@ export { applyKimiCodeProviderConfig, applyMoonshotConfig, applyMoonshotProviderConfig, + applyNebiusConfig, + applyNebiusProviderConfig, applyOpenrouterConfig, applyOpenrouterProviderConfig, applySyntheticConfig, @@ -34,11 +36,13 @@ export { } from "./onboard-auth.config-opencode.js"; export { OPENROUTER_DEFAULT_MODEL_REF, + NEBIUS_DEFAULT_MODEL_REF, setAnthropicApiKey, setGeminiApiKey, setKimiCodeApiKey, setMinimaxApiKey, setMoonshotApiKey, + setNebiusApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, setSyntheticApiKey, diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index c5558596a..ea6212d6d 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -12,6 +12,7 @@ import { applyMinimaxApiConfig, applyMinimaxConfig, applyMoonshotConfig, + applyNebiusConfig, applyOpencodeZenConfig, applyOpenrouterConfig, applySyntheticConfig, @@ -23,6 +24,7 @@ import { setKimiCodeApiKey, setMinimaxApiKey, setMoonshotApiKey, + setNebiusApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, setSyntheticApiKey, @@ -336,6 +338,26 @@ export async function applyNonInteractiveAuthChoice(params: { if (authChoice === "minimax") return applyMinimaxConfig(nextConfig); + if (authChoice === "nebius-api-key") { + const resolved = await resolveNonInteractiveApiKey({ + provider: "nebius", + cfg: baseConfig, + flagValue: opts.nebiusApiKey, + flagName: "--nebius-api-key", + envVar: "NEBIUS_API_KEY", + runtime, + }); + if (!resolved) return null; + if (resolved.source !== "profile") await setNebiusApiKey(resolved.key); + nextConfig = applyAuthProfileConfig(nextConfig, { + profileId: "nebius:default", + provider: "nebius", + mode: "api_key", + }); + return applyNebiusConfig(nextConfig); +} + + if (authChoice === "opencode-zen") { const resolved = await resolveNonInteractiveApiKey({ provider: "opencode", diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index aa1d9afe0..e9e3795a8 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -27,6 +27,7 @@ export type AuthChoice = | "minimax" | "minimax-api" | "minimax-api-lightning" + | "nebius-api-key" | "opencode-zen" | "github-copilot" | "copilot-proxy" @@ -68,6 +69,7 @@ export type OnboardOptions = { geminiApiKey?: string; zaiApiKey?: string; minimaxApiKey?: string; + nebiusApiKey?: string; syntheticApiKey?: string; veniceApiKey?: string; opencodeZenApiKey?: string;