From 162db479c64de43fb77bdca2c6a8999a8a6643a4 Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Thu, 10 Sep 2026 13:04:05 +0200 Subject: [PATCH 01/13] feat(nebul): add Nebul provider with 16 models and hourly sync module --- .../alibaba/qwen3-30b-a3b-instruct-2507.toml | 28 +++ .../ministral-3-14b-instruct-2512.toml | 19 ++ packages/core/src/sync/index.ts | 5 +- packages/core/src/sync/providers/nebul.ts | 167 ++++++++++++++++++ packages/core/test/nebul.test.ts | 106 +++++++++++ providers/nebul/logo.svg | 5 + .../Qwen/Qwen3-30B-A3B-Instruct-2507.toml | 6 + .../nebul/models/Qwen/Qwen3.5-397B-A17B.toml | 13 ++ .../nebul/models/Qwen/Qwen3.8-27B-FP8.toml | 14 ++ .../deepseek-ai/DeepSeek-V4.1-Flash.toml | 13 ++ .../nebul/models/google/gemma-4-31B-it.toml | 8 + .../models/meta-models/muse-glimmer-30b.toml | 11 ++ .../Ministral-3-14B-Instruct-2512.toml | 5 + .../Mistral-Large-3-675B-Instruct-2512.toml | 6 + .../mistralai/Mistral-Medium-3.5-128B.toml | 10 ++ .../nebul/models/moonshotai/Kimi-K3.toml | 14 ++ .../NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml | 10 ++ ...VIDIA-Nemotron-3-Super-120B-A12B-BF16.toml | 13 ++ ...ron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml | 13 ++ .../nebul/models/openai/gpt-oss-120b.toml | 11 ++ .../nebul/models/zai-org/GLM-5.3-Flash.toml | 13 ++ providers/nebul/models/zai-org/GLM-5.3.toml | 14 ++ providers/nebul/provider.toml | 5 + 23 files changed, 508 insertions(+), 1 deletion(-) create mode 100644 models/alibaba/qwen3-30b-a3b-instruct-2507.toml create mode 100644 models/mistral/ministral-3-14b-instruct-2512.toml create mode 100644 packages/core/src/sync/providers/nebul.ts create mode 100644 packages/core/test/nebul.test.ts create mode 100644 providers/nebul/logo.svg create mode 100644 providers/nebul/models/Qwen/Qwen3-30B-A3B-Instruct-2507.toml create mode 100644 providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml create mode 100644 providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml create mode 100644 providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml create mode 100644 providers/nebul/models/google/gemma-4-31B-it.toml create mode 100644 providers/nebul/models/meta-models/muse-glimmer-30b.toml create mode 100644 providers/nebul/models/mistralai/Ministral-3-14B-Instruct-2512.toml create mode 100644 providers/nebul/models/mistralai/Mistral-Large-3-675B-Instruct-2512.toml create mode 100644 providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml create mode 100644 providers/nebul/models/moonshotai/Kimi-K3.toml create mode 100644 providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml create mode 100644 providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml create mode 100644 providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml create mode 100644 providers/nebul/models/openai/gpt-oss-120b.toml create mode 100644 providers/nebul/models/zai-org/GLM-5.3-Flash.toml create mode 100644 providers/nebul/models/zai-org/GLM-5.3.toml create mode 100644 providers/nebul/provider.toml diff --git a/models/alibaba/qwen3-30b-a3b-instruct-2507.toml b/models/alibaba/qwen3-30b-a3b-instruct-2507.toml new file mode 100644 index 00000000000..94b773e0da2 --- /dev/null +++ b/models/alibaba/qwen3-30b-a3b-instruct-2507.toml @@ -0,0 +1,28 @@ +# Sources (accessed 2026-09-10): +# https://huggingface.co/Qwen/Qwen3-30B-A3B-Instruct-2507 +# Hub createdAt 2025-07-28 is the release; lastModified 2025-09-17 is the latest revision. + +name = "Qwen3 30B A3B Instruct 2507" +description = "Updated non-thinking Qwen3 MoE with long context for instruction following and tool use" +family = "qwen" +release_date = "2025-07-28" +last_updated = "2025-09-17" +attachment = false +reasoning = false +temperature = true +tool_call = true +structured_output = true +open_weights = true +license = "Apache 2.0" + +[limit] +context = 262_144 +output = 16_384 + +[modalities] +input = ["text"] +output = ["text"] + +[[weights]] +label = "Hugging Face" +url = "https://huggingface.co/Qwen/Qwen3-30B-A3B-Instruct-2507" diff --git a/models/mistral/ministral-3-14b-instruct-2512.toml b/models/mistral/ministral-3-14b-instruct-2512.toml new file mode 100644 index 00000000000..7cd61ebb5a3 --- /dev/null +++ b/models/mistral/ministral-3-14b-instruct-2512.toml @@ -0,0 +1,19 @@ +name = "Ministral 3 14B Instruct" +description = "Efficient Mistral edge model balancing speed and cost for general-purpose tasks" +family = "ministral" +release_date = "2025-12-02" +last_updated = "2025-12-02" +attachment = true +reasoning = false +temperature = true +tool_call = true +structured_output = true +open_weights = false + +[limit] +context = 262_144 +output = 262_144 + +[modalities] +input = ["text", "image"] +output = ["text"] diff --git a/packages/core/src/sync/index.ts b/packages/core/src/sync/index.ts index 7f88aacb5bb..22a37b91b10 100644 --- a/packages/core/src/sync/index.ts +++ b/packages/core/src/sync/index.ts @@ -27,6 +27,7 @@ import { kilo } from "./providers/kilo.js"; import { llmgateway, llmgatewayProviders } from "./providers/llmgateway.js"; import { mergeGateway } from "./providers/merge-gateway.js"; import { meta } from "./providers/meta.js"; +import { nebul } from "./providers/nebul.js"; import { nanoGpt } from "./providers/nano-gpt.js"; import { openai } from "./providers/openai.js"; import { ofox } from "./providers/ofox.js"; @@ -152,6 +153,7 @@ export const providers: { "llmgateway-providers": SyncProvider; "merge-gateway": SyncProvider; meta: SyncProvider; + nebul: SyncProvider; "nano-gpt": SyncProvider; ofox: SyncProvider; openai: SyncProvider; @@ -187,6 +189,7 @@ export const providers: { "llmgateway-providers": llmgatewayProviders, "merge-gateway": mergeGateway, meta, + nebul, "nano-gpt": nanoGpt, ofox, openai, @@ -219,7 +222,7 @@ export const groups = { "vercel", ], cloudflare: ["cloudflare-ai-gateway", "cloudflare-workers-ai"], - direct: ["ambient", "anthropic", "baseten", "chutes", "cortecs", "deepinfra", "digitalocean", "github-copilot", "google", "hyper", "meta", "openai", "ovhcloud", "pioneer", "tinfoil", "venice", "wandb", "xai"], + direct: ["ambient", "anthropic", "baseten", "chutes", "cortecs", "deepinfra", "digitalocean", "github-copilot", "google", "hyper", "meta", "nebul", "openai", "ovhcloud", "pioneer", "tinfoil", "venice", "wandb", "xai"], } as const; type ProviderID = keyof typeof providers; diff --git a/packages/core/src/sync/providers/nebul.ts b/packages/core/src/sync/providers/nebul.ts new file mode 100644 index 00000000000..ea29002e259 --- /dev/null +++ b/packages/core/src/sync/providers/nebul.ts @@ -0,0 +1,167 @@ +import { existsSync, readdirSync } from "node:fs"; +import path from "node:path"; +import { z } from "zod"; + +import type { ExistingModel, SyncProvider, SyncedModel } from "../index.js"; +import { factorBaseModel } from "./openrouter.js"; + +const API_ENDPOINT = "https://api.inference.nebul.io/model/info"; +const MODELS_DIR = path.join(import.meta.dirname, "..", "..", "..", "..", "..", "models"); + +// Served org prefix -> models/ metadata namespace (HF org names differ from catalog labs). +const ORG_TO_MODEL_PROVIDER: Record = { + "deepseek-ai": "deepseek", + google: "google", + "meta-models": "meta", + mistralai: "mistral", + moonshotai: "moonshotai", + nvidia: "nvidia", + openai: "openai", + Qwen: "alibaba", + "zai-org": "zhipuai", +}; + +// Served IDs whose canonical metadata lives under a differently-named lab entry. +const BASE_MODEL_ALIASES: Record = { + "mistralai/Mistral-Large-3-675B-Instruct-2512": "mistral/mistral-large-2512", + "mistralai/Mistral-Medium-3.5-128B": "mistral/mistral-medium-2604", +}; + +// The synthetic health-check model and serving artifacts that must not enter the catalog. +const PING_MODEL = "Nebul/Ping"; +const DENYLIST = /OCR|Qwen3Guard/i; + +// Deprecated server-side (descriptions point at GLM-5.3) but not flagged by /model/info. +const DEPRECATED = new Set(["zai-org/GLM-5.1-FP8", "zai-org/GLM-5.2-FP8"]); + +const EffortValues = z.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max", "default"]); + +const ModelInfo = z.object({ + description: z.string().nullable().optional(), + huggingface_id: z.string().nullable().optional(), + input_cost_per_1m_tokens: z.number().nullable().optional(), + output_cost_per_1m_tokens: z.number().nullable().optional(), + cache_read_input_cost_per_1m_tokens: z.number().nullable().optional(), + max_input_tokens: z.number().nullable().optional(), + mode: z.string().nullable(), + model_type: z.string().nullable(), + reasoning_efforts: z.array(EffortValues).nullable().optional(), +}).passthrough(); + +export const NebulEntry = z.object({ + model_info: ModelInfo, + model_name: z.string().min(1), +}).passthrough(); + +export const NebulResponse = z.object({ + data: z.array(NebulEntry), +}).passthrough(); + +export type NebulEntry = z.infer; + +export const nebul = { + id: "nebul", + name: "Nebul", + modelsDir: "providers/nebul/models", + async fetchModels() { + const response = await fetch(API_ENDPOINT); + if (!response.ok) { + throw new Error(`Nebul models request failed: ${response.status} ${response.statusText}`); + } + return response.json(); + }, + parseModels(raw) { + return NebulResponse.parse(raw).data; + }, + // Unauthenticated /model/info is the authoritative catalog: entries removed + // server-side are removed here (deleteMissing defaults on), and new resolvable + // chat models are created with base_model overrides only. + translateModel(entry, context) { + if (!isCatalogChatModel(entry)) return undefined; + const id = entry.model_name; + const info = entry.model_info; + const baseModel = resolveBaseModel(id, info.huggingface_id ?? undefined); + if (baseModel === undefined) return undefined; + if (info.input_cost_per_1m_tokens == null || info.output_cost_per_1m_tokens == null || info.max_input_tokens == null) return undefined; + const values = { + reasoning_options: buildReasoningOptions(entry, context.existing(id)), + cost: { + input: info.input_cost_per_1m_tokens, + output: info.output_cost_per_1m_tokens, + cache_read: info.cache_read_input_cost_per_1m_tokens ?? undefined, + }, + limit: { context: info.max_input_tokens }, + }; + return { + id, + model: factorBaseModel(baseModel, values, values.limit) as SyncedModel, + }; + }, + // Only report chat models whose base_model could not be resolved; filtered + // serving artifacts (embeddings, rerankers, the ping model) skip silently. + sourceID(entry: NebulEntry) { + return isCatalogChatModel(entry) ? entry.model_name : undefined; + }, + skippedNotice(ids) { + if (ids.length === 0) return []; + return [ + `Nebul models could not be resolved to lab metadata and need hand-authored base_model targets:`, + ids.map((id) => `\`${id}\``).join(", "), + ]; + }, +} satisfies SyncProvider; + +function isCatalogChatModel(entry: NebulEntry): boolean { + const info = entry.model_info; + return info.model_type === "llm" && info.mode === "chat" + && entry.model_name !== PING_MODEL && !DEPRECATED.has(entry.model_name) && !DENYLIST.test(entry.model_name); +} + +function buildReasoningOptions(entry: NebulEntry, existing: ExistingModel | undefined) { + const efforts = entry.model_info.reasoning_efforts ?? []; + if (efforts.length === 0) return existing?.reasoning_options; + return [{ type: "effort" as const, values: efforts }]; +} + +function resolveBaseModel(servedID: string, huggingfaceID: string | undefined): string | undefined { + return baseModelCandidates(servedID, huggingfaceID).find(canonicalExists); +} + +// existsSync is case-insensitive on Windows/macOS; verify the real on-disk filename case +// so the resolved base_model matches the canonical metadata exactly (and CI on Linux). +function canonicalExists(candidate: string): boolean { + const file = path.join(MODELS_DIR, `${candidate}.toml`); + if (!existsSync(file)) return false; + try { + return readdirSync(path.dirname(file)).includes(path.basename(file)); + } catch { + return false; + } +} + +function baseModelCandidates(servedID: string, huggingfaceID: string | undefined): string[] { + const alias = BASE_MODEL_ALIASES[servedID]; + const servedCandidate = mapOrgToCandidate(servedID); + const hfCandidate = huggingfaceID === undefined ? undefined : mapOrgToCandidate(huggingfaceID); + return [ + ...new Set([alias, servedCandidate, hfCandidate, ...quantizationStripped(hfCandidate), ...quantizationStripped(servedCandidate)]).values(), + ].filter((candidate): candidate is string => candidate !== undefined); +} + +function mapOrgToCandidate(id: string): string | undefined { + const [org, ...modelParts] = id.split("/"); + if (org === undefined || modelParts.length === 0) return undefined; + const provider = ORG_TO_MODEL_PROVIDER[org]; + if (provider === undefined) return undefined; + return `${provider}/${modelParts.join("/").toLowerCase()}`; +} + +// Hosts serve quantized checkpoints (e.g. -FP8, -BF16) of weights whose canonical +// metadata is published for the base precision; try those names without the suffix. +// NVIDIA also prefixes checkpoints with "NVIDIA-", which the metadata names drop. +function quantizationStripped(candidate: string | undefined): string[] { + if (candidate === undefined) return []; + const withoutQuant = candidate.replace(/-(fp8|bf16|fp4|int8)$/i, ""); + const withoutPrefix = withoutQuant.replace(/nvidia-/, ""); + return withoutQuant === candidate ? [] : [...new Set([withoutQuant, withoutPrefix])].filter((value) => value !== candidate); +} diff --git a/packages/core/test/nebul.test.ts b/packages/core/test/nebul.test.ts new file mode 100644 index 00000000000..e2d2aed1fc1 --- /dev/null +++ b/packages/core/test/nebul.test.ts @@ -0,0 +1,106 @@ +import { expect, test } from "bun:test"; + +import type { ExistingModel } from "../src/sync/index.js"; +import { + NebulEntry, + NebulResponse, + nebul, +} from "../src/sync/providers/nebul.js"; + +function nebulEntry(model_name?: string, model_info: Record = {}): NebulEntry { + return NebulEntry.parse({ + model_name: model_name ?? "zai-org/GLM-5.3", + model_info: { + description: "test", + huggingface_id: "zai-org/GLM-5.3", + input_cost_per_1m_tokens: 1.47, + output_cost_per_1m_tokens: 4.62, + cache_read_input_cost_per_1m_tokens: 0.35, + max_input_tokens: 1_000_000, + mode: "chat", + model_type: "llm", + reasoning_efforts: ["low", "high", "max"], + ...model_info, + }, + }); +} + +function existingWith(reasoning_options: ExistingModel["reasoning_options"]): ExistingModel { + return { reasoning_options } as ExistingModel; +} + +const context = (existing: ExistingModel | undefined) => ({ existing: () => existing }); + +test("syncs Nebul's factored overrides against resolved lab metadata", () => { + const translated = nebul.translateModel(nebulEntry("zai-org/GLM-5.3", { max_input_tokens: 1_048_576 }), context(undefined)); + expect(translated).toMatchObject({ + id: "zai-org/GLM-5.3", + model: { + base_model: "zhipuai/glm-5.3", + cost: { input: 1.47, output: 4.62, cache_read: 0.35 }, + limit: { context: 1_048_576 }, + reasoning_options: [{ type: "effort", values: ["low", "high", "max"] }], + }, + }); +}); + +test("preserves authored reasoning controls when the host exposes no efforts", () => { + const authored = [{ type: "toggle" as const }]; + const translated = nebul.translateModel(nebulEntry("zai-org/GLM-5.3", { reasoning_efforts: [] }), context(existingWith(authored))); + expect(translated?.model.reasoning_options).toEqual(authored); +}); + +test("resolves base models across org renames and quantization suffixes", () => { + const cases: [string, string | null, string][] = [ + ["Qwen/Qwen3.8-27B-FP8", "Qwen/Qwen3.8-27B-FP8", "alibaba/qwen3.8-27b"], + ["nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16", "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16", "nvidia/nemotron-3-nano-30b-a3b"], + ["mistralai/Mistral-Large-3-675B-Instruct-2512", "mistralai/Mistral-Large-3-675B-Instruct-2512", "mistral/mistral-large-2512"], + ["mistralai/Mistral-Medium-3.5-128B", "mistralai/Mistral-Medium-3.5-128B", "mistral/mistral-medium-2604"], + ]; + for (const [model_name, huggingface_id, expected] of cases) { + const entry = nebulEntry(model_name, { huggingface_id }); + expect(nebul.translateModel(entry, context(undefined))?.model.base_model).toBe(expected); + } +}); + +test("skips the ping model, serving artifacts, and deprecated entries silently", () => { + for (const model_name of ["Nebul/Ping", "zai-org/GLM-5.1-FP8", "zai-org/GLM-5.2-FP8", "Nebul-OCR/Some-OCR", "Qwen/Qwen3Guard-Something"]) { + const entry = nebulEntry(model_name, {}); + expect(nebul.translateModel(entry, context(undefined))).toBeUndefined(); + expect(nebul.sourceID(entry)).toBeUndefined(); + } +}); + +test("skips embeddings and rerankers while reporting unresolvable chat models", () => { + const embedding = nebulEntry("BAAI/bge-m3", { model_type: "embedding" }); + expect(nebul.translateModel(embedding, context(undefined))).toBeUndefined(); + expect(nebul.sourceID(embedding)).toBeUndefined(); + + const chat = nebulEntry("mistralai/Mistral-Large-3-675B-Instruct-2512", { huggingface_id: null }); + expect(nebul.translateModel(chat, context(undefined))).toBeDefined(); + expect(nebul.sourceID(chat)).toBe("mistralai/Mistral-Large-3-675B-Instruct-2512"); +}); + +test("skips chat models whose pricing or context is absent instead of crashing", () => { + const unpriced = nebulEntry("zai-org/GLM-5.3", { input_cost_per_1m_tokens: null, output_cost_per_1m_tokens: null, max_input_tokens: null }); + expect(nebul.translateModel(unpriced, context(undefined))).toBeUndefined(); + expect(nebul.sourceID(unpriced)).toBe("zai-org/GLM-5.3"); +}); + +test("parses nullable serving artifacts and unknown-host metadata from /model/info", () => { + const parsed = NebulResponse.parse({ + data: [ + { model_name: "Some/Embedding", model_info: { mode: null, model_type: "embedding" } }, + { model_name: "Some/Chat", model_info: { mode: "chat", model_type: "llm", unknown_host_field: true } }, + ], + }); + expect(parsed.data).toHaveLength(2); +}); + +test("rejects unknown reasoning effort values from the host", () => { + expect(() => + NebulResponse.parse({ + data: [{ model_name: "Some/Chat", model_info: { mode: "chat", model_type: "llm", reasoning_efforts: ["ultra"] } }], + }), + ).toThrow(); +}); diff --git a/providers/nebul/logo.svg b/providers/nebul/logo.svg new file mode 100644 index 00000000000..33daff15792 --- /dev/null +++ b/providers/nebul/logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/providers/nebul/models/Qwen/Qwen3-30B-A3B-Instruct-2507.toml b/providers/nebul/models/Qwen/Qwen3-30B-A3B-Instruct-2507.toml new file mode 100644 index 00000000000..2396ed198f9 --- /dev/null +++ b/providers/nebul/models/Qwen/Qwen3-30B-A3B-Instruct-2507.toml @@ -0,0 +1,6 @@ +base_model = "alibaba/qwen3-30b-a3b-instruct-2507" + +[cost] +input = 0.21 +output = 0.74 +cache_read = 0.05 diff --git a/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml b/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml new file mode 100644 index 00000000000..b9cc203d0a9 --- /dev/null +++ b/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml @@ -0,0 +1,13 @@ +base_model = "alibaba/qwen3.5-397b-a17b" + +# Controls per alibaba's official entry: thinking toggle + budget +[[reasoning_options]] +type = "toggle" + +[[reasoning_options]] +type = "budget_tokens" + +[cost] +input = 0.63 +output = 3.78 +cache_read = 0.15 diff --git a/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml b/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml new file mode 100644 index 00000000000..e0d778d4b2a --- /dev/null +++ b/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml @@ -0,0 +1,14 @@ +base_model = "alibaba/qwen3.8-27b" + +# Controls per deepinfra peer: thinking toggle + efforts +[[reasoning_options]] +type = "toggle" + +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "xhigh"] + +[cost] +input = 0.21 +output = 0.74 +cache_read = 0.05 diff --git a/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml b/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml new file mode 100644 index 00000000000..d00053543cb --- /dev/null +++ b/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml @@ -0,0 +1,13 @@ +base_model = "deepseek/deepseek-v4.1-flash" + +# Efforts: reasoning_effort = low|high|max (from /model/info reasoning_efforts) +[[reasoning_options]] +type = "effort" +values = ["low", "high", "max"] + +[cost] +input = 0.2 +output = 0.7 + +[limit] +context = 1_048_576 diff --git a/providers/nebul/models/google/gemma-4-31B-it.toml b/providers/nebul/models/google/gemma-4-31B-it.toml new file mode 100644 index 00000000000..4c8e563df73 --- /dev/null +++ b/providers/nebul/models/google/gemma-4-31B-it.toml @@ -0,0 +1,8 @@ +base_model = "google/gemma-4-31b-it" + +[[reasoning_options]] +type = "toggle" + +[cost] +input = 0.42 +output = 1.05 diff --git a/providers/nebul/models/meta-models/muse-glimmer-30b.toml b/providers/nebul/models/meta-models/muse-glimmer-30b.toml new file mode 100644 index 00000000000..0daac5a89cc --- /dev/null +++ b/providers/nebul/models/meta-models/muse-glimmer-30b.toml @@ -0,0 +1,11 @@ +base_model = "meta/muse-glimmer-30b" + +# Efforts per fireworks peer (models without API-exposed levels keep lab controls) +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "xhigh"] + +[cost] +input = 0.7 +output = 3.5 +cache_read = 0.35 diff --git a/providers/nebul/models/mistralai/Ministral-3-14B-Instruct-2512.toml b/providers/nebul/models/mistralai/Ministral-3-14B-Instruct-2512.toml new file mode 100644 index 00000000000..d2768469116 --- /dev/null +++ b/providers/nebul/models/mistralai/Ministral-3-14B-Instruct-2512.toml @@ -0,0 +1,5 @@ +base_model = "mistral/ministral-3-14b-instruct-2512" + +[cost] +input = 0.73 +output = 3.63 diff --git a/providers/nebul/models/mistralai/Mistral-Large-3-675B-Instruct-2512.toml b/providers/nebul/models/mistralai/Mistral-Large-3-675B-Instruct-2512.toml new file mode 100644 index 00000000000..992a53758fd --- /dev/null +++ b/providers/nebul/models/mistralai/Mistral-Large-3-675B-Instruct-2512.toml @@ -0,0 +1,6 @@ +base_model = "mistral/mistral-large-2512" + +[cost] +input = 0.6 +output = 1.73 +cache_read = 0.15 diff --git a/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml b/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml new file mode 100644 index 00000000000..331f2d4f281 --- /dev/null +++ b/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml @@ -0,0 +1,10 @@ +base_model = "mistral/mistral-medium-2604" + +# Efforts: reasoning_effort = low|medium|high|max (from /model/info reasoning_efforts) +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "max"] + +[cost] +input = 1.65 +output = 8.25 diff --git a/providers/nebul/models/moonshotai/Kimi-K3.toml b/providers/nebul/models/moonshotai/Kimi-K3.toml new file mode 100644 index 00000000000..91c5bd93f7e --- /dev/null +++ b/providers/nebul/models/moonshotai/Kimi-K3.toml @@ -0,0 +1,14 @@ +base_model = "moonshotai/kimi-k3" + +# Controls per moonshot's official entry: thinking toggle + adaptive effort +[[reasoning_options]] +type = "toggle" + +[[reasoning_options]] +type = "effort" +values = ["low", "high", "max"] + +[cost] +input = 4.73 +output = 23.63 +cache_read = 1.13 diff --git a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml new file mode 100644 index 00000000000..4ae1416245d --- /dev/null +++ b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml @@ -0,0 +1,10 @@ +base_model = "nvidia/nemotron-3-nano-30b-a3b" + +# Nemotron 3 hybrid reasoning: on/off toggle (deepinfra peer) +[[reasoning_options]] +type = "toggle" + +[cost] +input = 1 +output = 3 +cache_read = 0.5 diff --git a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml new file mode 100644 index 00000000000..9c849214092 --- /dev/null +++ b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml @@ -0,0 +1,13 @@ +base_model = "nvidia/nemotron-3-super-120b-a12b" + +# Nemotron 3 hybrid reasoning: on/off toggle (crusoe peer) +[[reasoning_options]] +type = "toggle" + +[cost] +input = 0.32 +output = 0.69 +cache_read = 0.08 + +[limit] +context = 1_000_000 diff --git a/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml b/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml new file mode 100644 index 00000000000..e987675f816 --- /dev/null +++ b/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml @@ -0,0 +1,13 @@ +base_model = "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning" + +# Nemotron 3 hybrid reasoning: on/off toggle (crusoe sibling pattern) +[[reasoning_options]] +type = "toggle" + +[cost] +input = 0.16 +output = 1.05 +cache_read = 0.05 + +[limit] +context = 262_144 diff --git a/providers/nebul/models/openai/gpt-oss-120b.toml b/providers/nebul/models/openai/gpt-oss-120b.toml new file mode 100644 index 00000000000..8477b4b5638 --- /dev/null +++ b/providers/nebul/models/openai/gpt-oss-120b.toml @@ -0,0 +1,11 @@ +base_model = "openai/gpt-oss-120b" + +# Efforts: reasoning_effort = low|medium|high — the model's documented Chat Completions control +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high"] + +[cost] +input = 0.17 +output = 0.63 +cache_read = 0.05 diff --git a/providers/nebul/models/zai-org/GLM-5.3-Flash.toml b/providers/nebul/models/zai-org/GLM-5.3-Flash.toml new file mode 100644 index 00000000000..ba6964468d5 --- /dev/null +++ b/providers/nebul/models/zai-org/GLM-5.3-Flash.toml @@ -0,0 +1,13 @@ +base_model = "zhipuai/glm-5.3-flash" + +# Efforts: reasoning_effort = low|high|max on POST /v1/chat/completions (verified live 2026-09-09) +[[reasoning_options]] +type = "effort" +values = ["low", "high", "max"] + +[cost] +input = 0.21 +output = 0.63 + +[limit] +context = 1_048_572 diff --git a/providers/nebul/models/zai-org/GLM-5.3.toml b/providers/nebul/models/zai-org/GLM-5.3.toml new file mode 100644 index 00000000000..b1f38252c8e --- /dev/null +++ b/providers/nebul/models/zai-org/GLM-5.3.toml @@ -0,0 +1,14 @@ +base_model = "zhipuai/glm-5.3" + +# GLM-5.3 always reasons; efforts low|high|max per zhipuai's official entry +[[reasoning_options]] +type = "effort" +values = ["low", "high", "max"] + +[cost] +input = 1.47 +output = 4.62 +cache_read = 0.35 + +[limit] +context = 1_048_576 diff --git a/providers/nebul/provider.toml b/providers/nebul/provider.toml new file mode 100644 index 00000000000..dc44e31d87d --- /dev/null +++ b/providers/nebul/provider.toml @@ -0,0 +1,5 @@ +name = "Nebul" +npm = "@ai-sdk/openai-compatible" +api = "https://api.inference.nebul.io/v1" +env = ["NEBUL_API_KEY"] +doc = "https://docs.nebul.io" From 70b6735a90e4a2a1d5f6b8f7502818514fde8e25 Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Fri, 11 Sep 2026 14:48:47 +0200 Subject: [PATCH 02/13] fix(mistral): mark Ministral 3 14B Instruct 2512 as open weights (Apache 2.0) --- models/mistral/ministral-3-14b-instruct-2512.toml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/models/mistral/ministral-3-14b-instruct-2512.toml b/models/mistral/ministral-3-14b-instruct-2512.toml index 7cd61ebb5a3..c027b18cab5 100644 --- a/models/mistral/ministral-3-14b-instruct-2512.toml +++ b/models/mistral/ministral-3-14b-instruct-2512.toml @@ -1,3 +1,7 @@ +# Sources (accessed 2026-09-11): +# https://huggingface.co/mistralai/Ministral-3-14B-Instruct-2512 (Apache 2.0, public safetensors) +# https://mistral.ai/news/mistral-3 + name = "Ministral 3 14B Instruct" description = "Efficient Mistral edge model balancing speed and cost for general-purpose tasks" family = "ministral" @@ -8,7 +12,8 @@ reasoning = false temperature = true tool_call = true structured_output = true -open_weights = false +open_weights = true +license = "Apache 2.0" [limit] context = 262_144 @@ -17,3 +22,7 @@ output = 262_144 [modalities] input = ["text", "image"] output = ["text"] + +[[weights]] +label = "Hugging Face" +url = "https://huggingface.co/mistralai/Ministral-3-14B-Instruct-2512" From 1239e4407200191c5dd959f2777c8b8ab5225065 Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Fri, 11 Sep 2026 14:48:47 +0200 Subject: [PATCH 03/13] fix(nebul): drop reasoning controls not documented on Nebul's API --- providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml | 9 +++------ providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml | 10 +++------- .../nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml | 4 +++- providers/nebul/models/google/gemma-4-31B-it.toml | 5 +++-- .../nebul/models/meta-models/muse-glimmer-30b.toml | 7 +++---- providers/nebul/models/moonshotai/Kimi-K3.toml | 10 +++------- .../nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml | 6 +++--- .../nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml | 6 +++--- .../Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml | 6 +++--- 9 files changed, 27 insertions(+), 36 deletions(-) diff --git a/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml b/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml index b9cc203d0a9..1ff8b5e0bd1 100644 --- a/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml +++ b/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml @@ -1,11 +1,8 @@ +# Model reasons always-on; Nebul exposes no thinking toggle or budget field (its +# docs cover reasoning_effort only): https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "alibaba/qwen3.5-397b-a17b" -# Controls per alibaba's official entry: thinking toggle + budget -[[reasoning_options]] -type = "toggle" - -[[reasoning_options]] -type = "budget_tokens" +reasoning_options = [] [cost] input = 0.63 diff --git a/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml b/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml index e0d778d4b2a..ed940b6af30 100644 --- a/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml +++ b/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml @@ -1,12 +1,8 @@ +# Model reasons always-on; /model/info advertises no reasoning_efforts for it and +# Nebul's docs cover reasoning_effort only: https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "alibaba/qwen3.8-27b" -# Controls per deepinfra peer: thinking toggle + efforts -[[reasoning_options]] -type = "toggle" - -[[reasoning_options]] -type = "effort" -values = ["low", "medium", "xhigh"] +reasoning_options = [] [cost] input = 0.21 diff --git a/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml b/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml index d00053543cb..f9a3906bb2b 100644 --- a/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml +++ b/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml @@ -1,6 +1,8 @@ base_model = "deepseek/deepseek-v4.1-flash" -# Efforts: reasoning_effort = low|high|max (from /model/info reasoning_efforts) +# Efforts: reasoning_effort = low|high|max (advertised by /model/info reasoning_efforts). +# reasoning_effort is Nebul's only documented reasoning control; no separate on/off toggle. +# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning [[reasoning_options]] type = "effort" values = ["low", "high", "max"] diff --git a/providers/nebul/models/google/gemma-4-31B-it.toml b/providers/nebul/models/google/gemma-4-31B-it.toml index 4c8e563df73..b02d4867f35 100644 --- a/providers/nebul/models/google/gemma-4-31B-it.toml +++ b/providers/nebul/models/google/gemma-4-31B-it.toml @@ -1,7 +1,8 @@ +# Model reasons always-on; Nebul exposes no caller control for it (its docs cover +# reasoning_effort only): https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "google/gemma-4-31b-it" -[[reasoning_options]] -type = "toggle" +reasoning_options = [] [cost] input = 0.42 diff --git a/providers/nebul/models/meta-models/muse-glimmer-30b.toml b/providers/nebul/models/meta-models/muse-glimmer-30b.toml index 0daac5a89cc..7f04f392040 100644 --- a/providers/nebul/models/meta-models/muse-glimmer-30b.toml +++ b/providers/nebul/models/meta-models/muse-glimmer-30b.toml @@ -1,9 +1,8 @@ +# Model reasons always-on; /model/info advertises no reasoning_efforts for it and +# Nebul's docs cover reasoning_effort only: https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "meta/muse-glimmer-30b" -# Efforts per fireworks peer (models without API-exposed levels keep lab controls) -[[reasoning_options]] -type = "effort" -values = ["low", "medium", "high", "xhigh"] +reasoning_options = [] [cost] input = 0.7 diff --git a/providers/nebul/models/moonshotai/Kimi-K3.toml b/providers/nebul/models/moonshotai/Kimi-K3.toml index 91c5bd93f7e..b9e9431a442 100644 --- a/providers/nebul/models/moonshotai/Kimi-K3.toml +++ b/providers/nebul/models/moonshotai/Kimi-K3.toml @@ -1,12 +1,8 @@ +# Model reasons always-on; /model/info advertises no reasoning_efforts for it and +# Nebul's docs cover reasoning_effort only: https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "moonshotai/kimi-k3" -# Controls per moonshot's official entry: thinking toggle + adaptive effort -[[reasoning_options]] -type = "toggle" - -[[reasoning_options]] -type = "effort" -values = ["low", "high", "max"] +reasoning_options = [] [cost] input = 4.73 diff --git a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml index 4ae1416245d..b178db29362 100644 --- a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml +++ b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml @@ -1,8 +1,8 @@ +# Hybrid reasoning; Nebul exposes no caller control for it (its docs cover +# reasoning_effort only): https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "nvidia/nemotron-3-nano-30b-a3b" -# Nemotron 3 hybrid reasoning: on/off toggle (deepinfra peer) -[[reasoning_options]] -type = "toggle" +reasoning_options = [] [cost] input = 1 diff --git a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml index 9c849214092..b83ee9731f3 100644 --- a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml +++ b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml @@ -1,8 +1,8 @@ +# Hybrid reasoning; Nebul exposes no caller control for it (its docs cover +# reasoning_effort only): https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "nvidia/nemotron-3-super-120b-a12b" -# Nemotron 3 hybrid reasoning: on/off toggle (crusoe peer) -[[reasoning_options]] -type = "toggle" +reasoning_options = [] [cost] input = 0.32 diff --git a/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml b/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml index e987675f816..c2547361c3a 100644 --- a/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml +++ b/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml @@ -1,8 +1,8 @@ +# Hybrid reasoning; Nebul exposes no caller control for it (its docs cover +# reasoning_effort only): https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning" -# Nemotron 3 hybrid reasoning: on/off toggle (crusoe sibling pattern) -[[reasoning_options]] -type = "toggle" +reasoning_options = [] [cost] input = 0.16 From 4717cca8d9b35bf62a1c4e0e941f2bc7f4aed65d Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Fri, 11 Sep 2026 14:48:47 +0200 Subject: [PATCH 04/13] fix(nebul): preserve hand-authored non-effort reasoning controls during sync --- packages/core/src/sync/providers/nebul.ts | 5 ++++- packages/core/test/nebul.test.ts | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/core/src/sync/providers/nebul.ts b/packages/core/src/sync/providers/nebul.ts index ea29002e259..883c6f5ef91 100644 --- a/packages/core/src/sync/providers/nebul.ts +++ b/packages/core/src/sync/providers/nebul.ts @@ -117,10 +117,13 @@ function isCatalogChatModel(entry: NebulEntry): boolean { && entry.model_name !== PING_MODEL && !DEPRECATED.has(entry.model_name) && !DENYLIST.test(entry.model_name); } +// /model/info only describes the effort control; hand-authored toggle and +// budget_tokens options must survive rewrites, so replace just the effort entry. function buildReasoningOptions(entry: NebulEntry, existing: ExistingModel | undefined) { const efforts = entry.model_info.reasoning_efforts ?? []; if (efforts.length === 0) return existing?.reasoning_options; - return [{ type: "effort" as const, values: efforts }]; + const preserved = existing?.reasoning_options?.filter((option) => option.type !== "effort") ?? []; + return [...preserved, { type: "effort" as const, values: efforts }]; } function resolveBaseModel(servedID: string, huggingfaceID: string | undefined): string | undefined { diff --git a/packages/core/test/nebul.test.ts b/packages/core/test/nebul.test.ts index e2d2aed1fc1..88caba0a9d3 100644 --- a/packages/core/test/nebul.test.ts +++ b/packages/core/test/nebul.test.ts @@ -50,6 +50,20 @@ test("preserves authored reasoning controls when the host exposes no efforts", ( expect(translated?.model.reasoning_options).toEqual(authored); }); +test("preserves authored toggle and budget controls when the host advertises efforts", () => { + const authored = [ + { type: "toggle" as const }, + { type: "budget_tokens" as const }, + { type: "effort" as const, values: ["low"] }, + ]; + const translated = nebul.translateModel(nebulEntry("zai-org/GLM-5.3"), context(existingWith(authored))); + expect(translated?.model.reasoning_options).toEqual([ + { type: "toggle" }, + { type: "budget_tokens" }, + { type: "effort", values: ["low", "high", "max"] }, + ]); +}); + test("resolves base models across org renames and quantization suffixes", () => { const cases: [string, string | null, string][] = [ ["Qwen/Qwen3.8-27B-FP8", "Qwen/Qwen3.8-27B-FP8", "alibaba/qwen3.8-27b"], From 09a48a9e9a3b9bc066c7c9d2d3b6a01f6f5af7fe Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Fri, 11 Sep 2026 15:02:52 +0200 Subject: [PATCH 05/13] fix(nebul): keep hand-authored models when source data is incomplete --- packages/core/src/sync/providers/nebul.ts | 41 +++++++++++++++-------- packages/core/test/nebul.test.ts | 26 ++++++++++++++ 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/packages/core/src/sync/providers/nebul.ts b/packages/core/src/sync/providers/nebul.ts index 883c6f5ef91..a5b5bf408fc 100644 --- a/packages/core/src/sync/providers/nebul.ts +++ b/packages/core/src/sync/providers/nebul.ts @@ -80,22 +80,35 @@ export const nebul = { if (!isCatalogChatModel(entry)) return undefined; const id = entry.model_name; const info = entry.model_info; - const baseModel = resolveBaseModel(id, info.huggingface_id ?? undefined); - if (baseModel === undefined) return undefined; - if (info.input_cost_per_1m_tokens == null || info.output_cost_per_1m_tokens == null || info.max_input_tokens == null) return undefined; + const existing = context.existing(id); + // Existing entries must survive incomplete source data — a transient null + // price or an unresolved alias would otherwise delete the hand-authored + // TOML on the next run. They keep their authored base_model and cost/limit; + // only brand-new models need a fully-priced, resolvable source entry. + const baseModel = existing?.base_model ?? resolveBaseModel(id, info.huggingface_id ?? undefined); + const cost = info.input_cost_per_1m_tokens != null && info.output_cost_per_1m_tokens != null + ? { + input: info.input_cost_per_1m_tokens, + output: info.output_cost_per_1m_tokens, + cache_read: info.cache_read_input_cost_per_1m_tokens ?? undefined, + } + : existing?.cost; + const limit = info.max_input_tokens != null ? { context: info.max_input_tokens } : existing?.limit; + if (existing === undefined && (baseModel === undefined || cost === undefined || limit === undefined)) return undefined; const values = { - reasoning_options: buildReasoningOptions(entry, context.existing(id)), - cost: { - input: info.input_cost_per_1m_tokens, - output: info.output_cost_per_1m_tokens, - cache_read: info.cache_read_input_cost_per_1m_tokens ?? undefined, - }, - limit: { context: info.max_input_tokens }, - }; - return { - id, - model: factorBaseModel(baseModel, values, values.limit) as SyncedModel, + reasoning_options: buildReasoningOptions(entry, existing), + cost, + limit, }; + if (baseModel !== undefined) { + return { + id, + model: factorBaseModel(baseModel, values, limit) as SyncedModel, + }; + } + // Existing standalone definition whose served alias no longer resolves: + // keep the authored fields, refreshing only what /model/info still provides. + return { id, model: { ...existing, ...values } as SyncedModel }; }, // Only report chat models whose base_model could not be resolved; filtered // serving artifacts (embeddings, rerankers, the ping model) skip silently. diff --git a/packages/core/test/nebul.test.ts b/packages/core/test/nebul.test.ts index 88caba0a9d3..43b020113d6 100644 --- a/packages/core/test/nebul.test.ts +++ b/packages/core/test/nebul.test.ts @@ -64,6 +64,32 @@ test("preserves authored toggle and budget controls when the host advertises eff ]); }); +test("keeps existing entries when the source pricing or context is temporarily null", () => { + const existing = { + base_model: "zhipuai/glm-5.3", + cost: { input: 1.47, output: 4.62 }, + limit: { context: 1_048_576 }, + } as ExistingModel; + const translated = nebul.translateModel( + nebulEntry("zai-org/GLM-5.3", { input_cost_per_1m_tokens: null, output_cost_per_1m_tokens: null, max_input_tokens: null }), + context(existing), + ); + expect(translated).toMatchObject({ + id: "zai-org/GLM-5.3", + model: { base_model: "zhipuai/glm-5.3", cost: { input: 1.47, output: 4.62 }, limit: { context: 1_048_576 } }, + }); +}); + +test("keeps existing entries when the served alias no longer resolves to lab metadata", () => { + const existing = { + base_model: "zhipuai/glm-5.3", + cost: { input: 1.47, output: 4.62 }, + limit: { context: 1_048_576 }, + } as ExistingModel; + const translated = nebul.translateModel(nebulEntry("someorg/Unknown-Model", { huggingface_id: null }), context(existing)); + expect(translated?.model.base_model).toBe("zhipuai/glm-5.3"); +}); + test("resolves base models across org renames and quantization suffixes", () => { const cases: [string, string | null, string][] = [ ["Qwen/Qwen3.8-27B-FP8", "Qwen/Qwen3.8-27B-FP8", "alibaba/qwen3.8-27b"], From ad090c8662740942c38c9ab629a7dd0cb45f2b2f Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Fri, 11 Sep 2026 15:02:53 +0200 Subject: [PATCH 06/13] fix(nebul): cite /model/info as the source of Mistral-Medium-3.5 effort values --- providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml b/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml index 331f2d4f281..e00a2b8a97a 100644 --- a/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml +++ b/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml @@ -1,6 +1,8 @@ base_model = "mistral/mistral-medium-2604" -# Efforts: reasoning_effort = low|medium|high|max (from /model/info reasoning_efforts) +# Efforts: reasoning_effort = low|medium|high|max — exactly /model/info's advertised +# reasoning_efforts for this model, superseding the lab's none|high set. +# https://api.inference.nebul.io/model/info [[reasoning_options]] type = "effort" values = ["low", "medium", "high", "max"] From 6e139d66741fe07e26ef342fd281364dc04d19a2 Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Fri, 11 Sep 2026 15:02:53 +0200 Subject: [PATCH 07/13] fix(mistral): cite HF card for the Ministral output limit --- models/mistral/ministral-3-14b-instruct-2512.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/models/mistral/ministral-3-14b-instruct-2512.toml b/models/mistral/ministral-3-14b-instruct-2512.toml index c027b18cab5..335a18814f2 100644 --- a/models/mistral/ministral-3-14b-instruct-2512.toml +++ b/models/mistral/ministral-3-14b-instruct-2512.toml @@ -1,6 +1,7 @@ # Sources (accessed 2026-09-11): # https://huggingface.co/mistralai/Ministral-3-14B-Instruct-2512 (Apache 2.0, public safetensors) # https://mistral.ai/news/mistral-3 +# limit.output 262_144 per the HF card's own usage example (MAX_TOK = max_tokens = 262144). name = "Ministral 3 14B Instruct" description = "Efficient Mistral edge model balancing speed and cost for general-purpose tasks" From 385e7bea138dfc975ebe868ce11259682218656e Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Fri, 11 Sep 2026 15:45:28 +0200 Subject: [PATCH 08/13] fix(nebul): keep sync-owned source comments in leading header blocks --- providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml | 4 ++-- providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml | 4 ++-- providers/nebul/models/openai/gpt-oss-120b.toml | 2 +- providers/nebul/models/zai-org/GLM-5.3-Flash.toml | 2 +- providers/nebul/models/zai-org/GLM-5.3.toml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml b/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml index f9a3906bb2b..b1e92a13055 100644 --- a/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml +++ b/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml @@ -1,8 +1,8 @@ -base_model = "deepseek/deepseek-v4.1-flash" - # Efforts: reasoning_effort = low|high|max (advertised by /model/info reasoning_efforts). # reasoning_effort is Nebul's only documented reasoning control; no separate on/off toggle. # https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning +base_model = "deepseek/deepseek-v4.1-flash" + [[reasoning_options]] type = "effort" values = ["low", "high", "max"] diff --git a/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml b/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml index e00a2b8a97a..c790f7c2f37 100644 --- a/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml +++ b/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml @@ -1,8 +1,8 @@ -base_model = "mistral/mistral-medium-2604" - # Efforts: reasoning_effort = low|medium|high|max — exactly /model/info's advertised # reasoning_efforts for this model, superseding the lab's none|high set. # https://api.inference.nebul.io/model/info +base_model = "mistral/mistral-medium-2604" + [[reasoning_options]] type = "effort" values = ["low", "medium", "high", "max"] diff --git a/providers/nebul/models/openai/gpt-oss-120b.toml b/providers/nebul/models/openai/gpt-oss-120b.toml index 8477b4b5638..e3aab6851f9 100644 --- a/providers/nebul/models/openai/gpt-oss-120b.toml +++ b/providers/nebul/models/openai/gpt-oss-120b.toml @@ -1,6 +1,6 @@ +# Efforts: reasoning_effort = low|medium|high — the model's documented Chat Completions control base_model = "openai/gpt-oss-120b" -# Efforts: reasoning_effort = low|medium|high — the model's documented Chat Completions control [[reasoning_options]] type = "effort" values = ["low", "medium", "high"] diff --git a/providers/nebul/models/zai-org/GLM-5.3-Flash.toml b/providers/nebul/models/zai-org/GLM-5.3-Flash.toml index ba6964468d5..eef2f917969 100644 --- a/providers/nebul/models/zai-org/GLM-5.3-Flash.toml +++ b/providers/nebul/models/zai-org/GLM-5.3-Flash.toml @@ -1,6 +1,6 @@ +# Efforts: reasoning_effort = low|high|max on POST /v1/chat/completions (verified live 2026-09-09) base_model = "zhipuai/glm-5.3-flash" -# Efforts: reasoning_effort = low|high|max on POST /v1/chat/completions (verified live 2026-09-09) [[reasoning_options]] type = "effort" values = ["low", "high", "max"] diff --git a/providers/nebul/models/zai-org/GLM-5.3.toml b/providers/nebul/models/zai-org/GLM-5.3.toml index b1f38252c8e..937d438b7fe 100644 --- a/providers/nebul/models/zai-org/GLM-5.3.toml +++ b/providers/nebul/models/zai-org/GLM-5.3.toml @@ -1,6 +1,6 @@ +# GLM-5.3 always reasons; efforts low|high|max per zhipuai's official entry base_model = "zhipuai/glm-5.3" -# GLM-5.3 always reasons; efforts low|high|max per zhipuai's official entry [[reasoning_options]] type = "effort" values = ["low", "high", "max"] From f3a1584fc642455c9da3bccfadc41c74270bb696 Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Fri, 11 Sep 2026 16:03:19 +0200 Subject: [PATCH 09/13] fix(nebul): author interleaved reasoning side channels Nebul's docs map each family to a trace channel: reasoning_content for OpenAI-style models, the Nemotron family, Qwen3 thinking variants (plus DeepSeek-V4.1-Flash, Kimi-K3, Mistral-Medium-3.5 and muse-glimmer-30b, confirmed against the serving stack), message.reasoning for GLM-5.x and inline for Gemma. Author interleaved on all 13 reasoners (interleaved = true where the schema has no named field) and carry existing?.interleaved through translateModel so sync never drops it. --- packages/core/src/sync/providers/nebul.ts | 1 + packages/core/test/nebul.test.ts | 11 +++++++++++ providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml | 3 +++ providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml | 3 +++ .../nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml | 3 +++ providers/nebul/models/google/gemma-4-31B-it.toml | 3 +++ .../nebul/models/meta-models/muse-glimmer-30b.toml | 3 +++ .../models/mistralai/Mistral-Medium-3.5-128B.toml | 3 +++ providers/nebul/models/moonshotai/Kimi-K3.toml | 3 +++ .../nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml | 3 +++ .../NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml | 3 +++ .../Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml | 3 +++ providers/nebul/models/openai/gpt-oss-120b.toml | 3 +++ providers/nebul/models/zai-org/GLM-5.3-Flash.toml | 3 +++ providers/nebul/models/zai-org/GLM-5.3.toml | 3 +++ 15 files changed, 51 insertions(+) diff --git a/packages/core/src/sync/providers/nebul.ts b/packages/core/src/sync/providers/nebul.ts index a5b5bf408fc..6088cce1b9b 100644 --- a/packages/core/src/sync/providers/nebul.ts +++ b/packages/core/src/sync/providers/nebul.ts @@ -96,6 +96,7 @@ export const nebul = { const limit = info.max_input_tokens != null ? { context: info.max_input_tokens } : existing?.limit; if (existing === undefined && (baseModel === undefined || cost === undefined || limit === undefined)) return undefined; const values = { + interleaved: existing?.interleaved, reasoning_options: buildReasoningOptions(entry, existing), cost, limit, diff --git a/packages/core/test/nebul.test.ts b/packages/core/test/nebul.test.ts index 43b020113d6..543fd76fa87 100644 --- a/packages/core/test/nebul.test.ts +++ b/packages/core/test/nebul.test.ts @@ -64,6 +64,17 @@ test("preserves authored toggle and budget controls when the host advertises eff ]); }); +test("carries authored interleaved through sync", () => { + const inline = nebul.translateModel(nebulEntry("zai-org/GLM-5.3"), context({ interleaved: true } as ExistingModel)); + expect(inline?.model.interleaved).toBe(true); + + const named = nebul.translateModel( + nebulEntry("deepseek-ai/DeepSeek-V4.1-Flash"), + context({ interleaved: { field: "reasoning_content" } } as ExistingModel), + ); + expect(named?.model.interleaved).toEqual({ field: "reasoning_content" }); +}); + test("keeps existing entries when the source pricing or context is temporarily null", () => { const existing = { base_model: "zhipuai/glm-5.3", diff --git a/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml b/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml index 1ff8b5e0bd1..19509361dbc 100644 --- a/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml +++ b/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml @@ -4,6 +4,9 @@ base_model = "alibaba/qwen3.5-397b-a17b" reasoning_options = [] +[interleaved] +field = "reasoning_content" + [cost] input = 0.63 output = 3.78 diff --git a/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml b/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml index ed940b6af30..b5588318994 100644 --- a/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml +++ b/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml @@ -4,6 +4,9 @@ base_model = "alibaba/qwen3.8-27b" reasoning_options = [] +[interleaved] +field = "reasoning_content" + [cost] input = 0.21 output = 0.74 diff --git a/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml b/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml index b1e92a13055..c39e6cb1602 100644 --- a/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml +++ b/providers/nebul/models/deepseek-ai/DeepSeek-V4.1-Flash.toml @@ -3,6 +3,9 @@ # https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "deepseek/deepseek-v4.1-flash" +[interleaved] +field = "reasoning_content" + [[reasoning_options]] type = "effort" values = ["low", "high", "max"] diff --git a/providers/nebul/models/google/gemma-4-31B-it.toml b/providers/nebul/models/google/gemma-4-31B-it.toml index b02d4867f35..756e52c6fa5 100644 --- a/providers/nebul/models/google/gemma-4-31B-it.toml +++ b/providers/nebul/models/google/gemma-4-31B-it.toml @@ -1,9 +1,12 @@ # Model reasons always-on; Nebul exposes no caller control for it (its docs cover # reasoning_effort only): https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning +# Traces arrive inline as inside the content (Nebul docs), so interleaved = true base_model = "google/gemma-4-31b-it" reasoning_options = [] +interleaved = true + [cost] input = 0.42 output = 1.05 diff --git a/providers/nebul/models/meta-models/muse-glimmer-30b.toml b/providers/nebul/models/meta-models/muse-glimmer-30b.toml index 7f04f392040..a224048aadf 100644 --- a/providers/nebul/models/meta-models/muse-glimmer-30b.toml +++ b/providers/nebul/models/meta-models/muse-glimmer-30b.toml @@ -4,6 +4,9 @@ base_model = "meta/muse-glimmer-30b" reasoning_options = [] +[interleaved] +field = "reasoning_content" + [cost] input = 0.7 output = 3.5 diff --git a/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml b/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml index c790f7c2f37..428929c4918 100644 --- a/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml +++ b/providers/nebul/models/mistralai/Mistral-Medium-3.5-128B.toml @@ -3,6 +3,9 @@ # https://api.inference.nebul.io/model/info base_model = "mistral/mistral-medium-2604" +[interleaved] +field = "reasoning_content" + [[reasoning_options]] type = "effort" values = ["low", "medium", "high", "max"] diff --git a/providers/nebul/models/moonshotai/Kimi-K3.toml b/providers/nebul/models/moonshotai/Kimi-K3.toml index b9e9431a442..2546ee5932f 100644 --- a/providers/nebul/models/moonshotai/Kimi-K3.toml +++ b/providers/nebul/models/moonshotai/Kimi-K3.toml @@ -4,6 +4,9 @@ base_model = "moonshotai/kimi-k3" reasoning_options = [] +[interleaved] +field = "reasoning_content" + [cost] input = 4.73 output = 23.63 diff --git a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml index b178db29362..c3018447d6d 100644 --- a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml +++ b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml @@ -4,6 +4,9 @@ base_model = "nvidia/nemotron-3-nano-30b-a3b" reasoning_options = [] +[interleaved] +field = "reasoning_content" + [cost] input = 1 output = 3 diff --git a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml index b83ee9731f3..abead25f50b 100644 --- a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml +++ b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml @@ -4,6 +4,9 @@ base_model = "nvidia/nemotron-3-super-120b-a12b" reasoning_options = [] +[interleaved] +field = "reasoning_content" + [cost] input = 0.32 output = 0.69 diff --git a/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml b/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml index c2547361c3a..94094ee7c95 100644 --- a/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml +++ b/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml @@ -4,6 +4,9 @@ base_model = "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning" reasoning_options = [] +[interleaved] +field = "reasoning_content" + [cost] input = 0.16 output = 1.05 diff --git a/providers/nebul/models/openai/gpt-oss-120b.toml b/providers/nebul/models/openai/gpt-oss-120b.toml index e3aab6851f9..7e53cb3b259 100644 --- a/providers/nebul/models/openai/gpt-oss-120b.toml +++ b/providers/nebul/models/openai/gpt-oss-120b.toml @@ -1,6 +1,9 @@ # Efforts: reasoning_effort = low|medium|high — the model's documented Chat Completions control base_model = "openai/gpt-oss-120b" +[interleaved] +field = "reasoning_content" + [[reasoning_options]] type = "effort" values = ["low", "medium", "high"] diff --git a/providers/nebul/models/zai-org/GLM-5.3-Flash.toml b/providers/nebul/models/zai-org/GLM-5.3-Flash.toml index eef2f917969..892df7195e5 100644 --- a/providers/nebul/models/zai-org/GLM-5.3-Flash.toml +++ b/providers/nebul/models/zai-org/GLM-5.3-Flash.toml @@ -1,6 +1,9 @@ # Efforts: reasoning_effort = low|high|max on POST /v1/chat/completions (verified live 2026-09-09) +# Traces arrive in message.reasoning (Nebul docs), so interleaved = true base_model = "zhipuai/glm-5.3-flash" +interleaved = true + [[reasoning_options]] type = "effort" values = ["low", "high", "max"] diff --git a/providers/nebul/models/zai-org/GLM-5.3.toml b/providers/nebul/models/zai-org/GLM-5.3.toml index 937d438b7fe..0a964c0dda7 100644 --- a/providers/nebul/models/zai-org/GLM-5.3.toml +++ b/providers/nebul/models/zai-org/GLM-5.3.toml @@ -1,6 +1,9 @@ # GLM-5.3 always reasons; efforts low|high|max per zhipuai's official entry +# Traces arrive in message.reasoning (Nebul docs), so interleaved = true base_model = "zhipuai/glm-5.3" +interleaved = true + [[reasoning_options]] type = "effort" values = ["low", "high", "max"] From ed484bf2fa5b430654bb320953687129a184c3dc Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Fri, 11 Sep 2026 16:03:26 +0200 Subject: [PATCH 10/13] fix(nebul): sync writes effort-only options when the host advertises efforts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nebul's API has no toggle or budget control, so lab-style options must not be merged back into a rewritten entry — that would claim wire fields Nebul does not accept. When reasoning_efforts is advertised, the effort entry is the whole set; when it is not, authored options are kept. --- packages/core/src/sync/providers/nebul.ts | 9 +++++---- packages/core/test/nebul.test.ts | 8 ++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/core/src/sync/providers/nebul.ts b/packages/core/src/sync/providers/nebul.ts index 6088cce1b9b..3470534d554 100644 --- a/packages/core/src/sync/providers/nebul.ts +++ b/packages/core/src/sync/providers/nebul.ts @@ -131,13 +131,14 @@ function isCatalogChatModel(entry: NebulEntry): boolean { && entry.model_name !== PING_MODEL && !DEPRECATED.has(entry.model_name) && !DENYLIST.test(entry.model_name); } -// /model/info only describes the effort control; hand-authored toggle and -// budget_tokens options must survive rewrites, so replace just the effort entry. +// Nebul documents exactly one reasoning control: reasoning_effort. When the +// host advertises efforts, write that effort entry and nothing else — +// lab-style toggles or budgets are not supported on this API. When it +// advertises none, keep the authored options. function buildReasoningOptions(entry: NebulEntry, existing: ExistingModel | undefined) { const efforts = entry.model_info.reasoning_efforts ?? []; if (efforts.length === 0) return existing?.reasoning_options; - const preserved = existing?.reasoning_options?.filter((option) => option.type !== "effort") ?? []; - return [...preserved, { type: "effort" as const, values: efforts }]; + return [{ type: "effort" as const, values: efforts }]; } function resolveBaseModel(servedID: string, huggingfaceID: string | undefined): string | undefined { diff --git a/packages/core/test/nebul.test.ts b/packages/core/test/nebul.test.ts index 543fd76fa87..ee84ec3165d 100644 --- a/packages/core/test/nebul.test.ts +++ b/packages/core/test/nebul.test.ts @@ -50,18 +50,14 @@ test("preserves authored reasoning controls when the host exposes no efforts", ( expect(translated?.model.reasoning_options).toEqual(authored); }); -test("preserves authored toggle and budget controls when the host advertises efforts", () => { +test("replaces authored options with the advertised effort entry when efforts are advertised", () => { const authored = [ { type: "toggle" as const }, { type: "budget_tokens" as const }, { type: "effort" as const, values: ["low"] }, ]; const translated = nebul.translateModel(nebulEntry("zai-org/GLM-5.3"), context(existingWith(authored))); - expect(translated?.model.reasoning_options).toEqual([ - { type: "toggle" }, - { type: "budget_tokens" }, - { type: "effort", values: ["low", "high", "max"] }, - ]); + expect(translated?.model.reasoning_options).toEqual([{ type: "effort", values: ["low", "high", "max"] }]); }); test("carries authored interleaved through sync", () => { From 3b0171319c12bced2d21c3b8ce3d4edc555f2df1 Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Fri, 11 Sep 2026 17:00:59 +0200 Subject: [PATCH 11/13] fix(nebul): author peer-derived effort sets for three unadvertised reasoners Nebul's docs state reasoning_effort is accepted for reasoning-capable models and ignored only by non-reasoning models, so an empty option set would falsely claim no caller control. Author effort options from same-surface peers intersected with Nebul's documented value set: Kimi-K3 low|high|max (Tinfoil), muse-glimmer-30b low|medium|high (Fireworks/Vercel, xhigh undocumented on Nebul), Qwen3.8-27B low|medium (OpenRouter, toggle absent on Nebul). --- packages/core/test/nebul.test.ts | 9 +++++++++ providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml | 12 ++++++++---- .../nebul/models/meta-models/muse-glimmer-30b.toml | 12 ++++++++---- providers/nebul/models/moonshotai/Kimi-K3.toml | 12 ++++++++---- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/packages/core/test/nebul.test.ts b/packages/core/test/nebul.test.ts index ee84ec3165d..77f76e53f3c 100644 --- a/packages/core/test/nebul.test.ts +++ b/packages/core/test/nebul.test.ts @@ -71,6 +71,15 @@ test("carries authored interleaved through sync", () => { expect(named?.model.interleaved).toEqual({ field: "reasoning_content" }); }); +test("keeps authored effort sets when the host advertises none", () => { + const authored = [{ type: "effort" as const, values: ["low", "high", "max"] }]; + const translated = nebul.translateModel( + nebulEntry("moonshotai/Kimi-K3", { reasoning_efforts: [] }), + context(existingWith(authored)), + ); + expect(translated?.model.reasoning_options).toEqual(authored); +}); + test("keeps existing entries when the source pricing or context is temporarily null", () => { const existing = { base_model: "zhipuai/glm-5.3", diff --git a/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml b/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml index b5588318994..c86aafb7ef4 100644 --- a/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml +++ b/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml @@ -1,12 +1,16 @@ -# Model reasons always-on; /model/info advertises no reasoning_efforts for it and -# Nebul's docs cover reasoning_effort only: https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning +# Effort: reasoning_effort (Nebul docs: accepted for reasoning-capable models, ignored +# only by non-reasoning models). Set is OpenRouter's low|medium|xhigh intersected with +# Nebul's documented value set; Nebul has no toggle and xhigh is undocumented on it. +# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "alibaba/qwen3.8-27b" -reasoning_options = [] - [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["low", "medium"] + [cost] input = 0.21 output = 0.74 diff --git a/providers/nebul/models/meta-models/muse-glimmer-30b.toml b/providers/nebul/models/meta-models/muse-glimmer-30b.toml index a224048aadf..b0b8eb26284 100644 --- a/providers/nebul/models/meta-models/muse-glimmer-30b.toml +++ b/providers/nebul/models/meta-models/muse-glimmer-30b.toml @@ -1,12 +1,16 @@ -# Model reasons always-on; /model/info advertises no reasoning_efforts for it and -# Nebul's docs cover reasoning_effort only: https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning +# Effort: reasoning_effort (Nebul docs: accepted for reasoning-capable models, ignored +# only by non-reasoning models). Set is Fireworks/Vercel's low|medium|high|xhigh +# intersected with Nebul's documented value set; xhigh is undocumented on Nebul. +# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "meta/muse-glimmer-30b" -reasoning_options = [] - [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high"] + [cost] input = 0.7 output = 3.5 diff --git a/providers/nebul/models/moonshotai/Kimi-K3.toml b/providers/nebul/models/moonshotai/Kimi-K3.toml index 2546ee5932f..fe1d9661f50 100644 --- a/providers/nebul/models/moonshotai/Kimi-K3.toml +++ b/providers/nebul/models/moonshotai/Kimi-K3.toml @@ -1,12 +1,16 @@ -# Model reasons always-on; /model/info advertises no reasoning_efforts for it and -# Nebul's docs cover reasoning_effort only: https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning +# Effort: reasoning_effort (Nebul docs: accepted for reasoning-capable models, ignored +# only by non-reasoning models). Set mirrors same-surface peer Tinfoil (low|high|max); +# /model/info does not enumerate efforts for this model. +# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "moonshotai/kimi-k3" -reasoning_options = [] - [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["low", "high", "max"] + [cost] input = 4.73 output = 23.63 From d3bdd8086642f1fb70d574bb68473eccc1ec50ea Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Fri, 11 Sep 2026 19:53:35 +0200 Subject: [PATCH 12/13] fix(nebul): author documented effort sets and fail closed on unadvertised reasoners --- .../ministral-3-14b-instruct-2512.toml | 6 +++++- packages/core/src/sync/providers/nebul.ts | 19 +++++++++++++++++-- packages/core/test/nebul.test.ts | 9 +++++++++ .../nebul/models/Qwen/Qwen3.5-397B-A17B.toml | 12 ++++++++---- .../nebul/models/Qwen/Qwen3.8-27B-FP8.toml | 6 +++--- .../nebul/models/google/gemma-4-31B-it.toml | 12 ++++++++---- .../NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml | 12 ++++++++---- ...VIDIA-Nemotron-3-Super-120B-A12B-BF16.toml | 12 ++++++++---- ...ron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml | 12 ++++++++---- 9 files changed, 74 insertions(+), 26 deletions(-) diff --git a/models/mistral/ministral-3-14b-instruct-2512.toml b/models/mistral/ministral-3-14b-instruct-2512.toml index 335a18814f2..946a426285c 100644 --- a/models/mistral/ministral-3-14b-instruct-2512.toml +++ b/models/mistral/ministral-3-14b-instruct-2512.toml @@ -1,7 +1,11 @@ # Sources (accessed 2026-09-11): # https://huggingface.co/mistralai/Ministral-3-14B-Instruct-2512 (Apache 2.0, public safetensors) # https://mistral.ai/news/mistral-3 -# limit.output 262_144 per the HF card's own usage example (MAX_TOK = max_tokens = 262144). +# limit.output 262_144: the HF card states a 256k (= 262,144) context window, its vLLM +# section defaults --max-model-len to 262144, and its own examples request +# max_tokens = 262144; Mistral publishes no separate generation cap. Current-gen Mistral +# flagships in this catalog (mistral-large-2512, mistral-medium-2604, devstral-2512) set +# the same output = 262_144. name = "Ministral 3 14B Instruct" description = "Efficient Mistral edge model balancing speed and cost for general-purpose tasks" diff --git a/packages/core/src/sync/providers/nebul.ts b/packages/core/src/sync/providers/nebul.ts index 3470534d554..51bac2061dc 100644 --- a/packages/core/src/sync/providers/nebul.ts +++ b/packages/core/src/sync/providers/nebul.ts @@ -3,7 +3,8 @@ import path from "node:path"; import { z } from "zod"; import type { ExistingModel, SyncProvider, SyncedModel } from "../index.js"; -import { factorBaseModel } from "./openrouter.js"; +import { MissingReasoningOptionsError } from "../missing-reasoning-options.js"; +import { factorBaseModel, modelMetadata } from "./openrouter.js"; const API_ENDPOINT = "https://api.inference.nebul.io/model/info"; const MODELS_DIR = path.join(import.meta.dirname, "..", "..", "..", "..", "..", "models"); @@ -95,6 +96,19 @@ export const nebul = { : existing?.cost; const limit = info.max_input_tokens != null ? { context: info.max_input_tokens } : existing?.limit; if (existing === undefined && (baseModel === undefined || cost === undefined || limit === undefined)) return undefined; + // Fail closed rather than emitting no reasoning_options: a reasoner with + // neither advertised efforts nor authored options would sync as an empty + // entry (no caller control). The runner keeps the file and lists it in the + // skipped notice so the options can be hand-authored. + const isReasoner = baseModel !== undefined + ? modelMetadata(baseModel).reasoning === true + : existing?.reasoning === true; + if (isReasoner && (info.reasoning_efforts ?? []).length === 0 && existing?.reasoning_options === undefined) { + throw new MissingReasoningOptionsError( + id, + `${id} is a reasoning model, but Nebul advertises no reasoning_efforts and the catalog entry has no reasoning_options; hand-author them`, + ); + } const values = { interleaved: existing?.interleaved, reasoning_options: buildReasoningOptions(entry, existing), @@ -134,7 +148,8 @@ function isCatalogChatModel(entry: NebulEntry): boolean { // Nebul documents exactly one reasoning control: reasoning_effort. When the // host advertises efforts, write that effort entry and nothing else — // lab-style toggles or budgets are not supported on this API. When it -// advertises none, keep the authored options. +// advertises none, keep the authored options; a reasoner with neither is +// rejected above so no empty options entry is ever synced. function buildReasoningOptions(entry: NebulEntry, existing: ExistingModel | undefined) { const efforts = entry.model_info.reasoning_efforts ?? []; if (efforts.length === 0) return existing?.reasoning_options; diff --git a/packages/core/test/nebul.test.ts b/packages/core/test/nebul.test.ts index 77f76e53f3c..cced07fce57 100644 --- a/packages/core/test/nebul.test.ts +++ b/packages/core/test/nebul.test.ts @@ -1,6 +1,7 @@ import { expect, test } from "bun:test"; import type { ExistingModel } from "../src/sync/index.js"; +import { MissingReasoningOptionsError } from "../src/sync/missing-reasoning-options.js"; import { NebulEntry, NebulResponse, @@ -80,6 +81,14 @@ test("keeps authored effort sets when the host advertises none", () => { expect(translated?.model.reasoning_options).toEqual(authored); }); +test("fails closed when a reasoner advertises no efforts and none are authored", () => { + const entry = nebulEntry("zai-org/GLM-5.3", { reasoning_efforts: [] }); + expect(() => nebul.translateModel(entry, context(undefined))).toThrow(MissingReasoningOptionsError); + expect(() => + nebul.translateModel(entry, context({ base_model: "zhipuai/glm-5.3" } as ExistingModel)), + ).toThrow(MissingReasoningOptionsError); +}); + test("keeps existing entries when the source pricing or context is temporarily null", () => { const existing = { base_model: "zhipuai/glm-5.3", diff --git a/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml b/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml index 19509361dbc..d6f8fe1901d 100644 --- a/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml +++ b/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml @@ -1,12 +1,16 @@ -# Model reasons always-on; Nebul exposes no thinking toggle or budget field (its -# docs cover reasoning_effort only): https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning +# Efforts mirror Nebul's documented host-wide reasoning_effort enum (its docs +# note the effort-to-budget mapping is model-specific guidance): +# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning. Same-surface +# peers expose a thinking toggle only, with no effort values to intersect. base_model = "alibaba/qwen3.5-397b-a17b" -reasoning_options = [] - [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "max"] + [cost] input = 0.63 output = 3.78 diff --git a/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml b/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml index c86aafb7ef4..6459efe54b7 100644 --- a/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml +++ b/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml @@ -1,6 +1,6 @@ # Effort: reasoning_effort (Nebul docs: accepted for reasoning-capable models, ignored -# only by non-reasoning models). Set is OpenRouter's low|medium|xhigh intersected with -# Nebul's documented value set; Nebul has no toggle and xhigh is undocumented on it. +# only by non-reasoning models). Set is OpenRouter's low|medium|xhigh plus Nebul's +# documented high; xhigh is undocumented on Nebul and no peer advertises max. # https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning base_model = "alibaba/qwen3.8-27b" @@ -9,7 +9,7 @@ field = "reasoning_content" [[reasoning_options]] type = "effort" -values = ["low", "medium"] +values = ["low", "medium", "high"] [cost] input = 0.21 diff --git a/providers/nebul/models/google/gemma-4-31B-it.toml b/providers/nebul/models/google/gemma-4-31B-it.toml index 756e52c6fa5..f7ca96e731a 100644 --- a/providers/nebul/models/google/gemma-4-31B-it.toml +++ b/providers/nebul/models/google/gemma-4-31B-it.toml @@ -1,12 +1,16 @@ -# Model reasons always-on; Nebul exposes no caller control for it (its docs cover -# reasoning_effort only): https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning +# Efforts mirror Nebul's documented host-wide reasoning_effort enum (its docs +# note the effort-to-budget mapping is model-specific guidance): +# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning. Same-surface +# peers expose a toggle only, with no effort values to intersect. # Traces arrive inline as inside the content (Nebul docs), so interleaved = true base_model = "google/gemma-4-31b-it" -reasoning_options = [] - interleaved = true +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "max"] + [cost] input = 0.42 output = 1.05 diff --git a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml index c3018447d6d..4c22765a143 100644 --- a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml +++ b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml @@ -1,12 +1,16 @@ -# Hybrid reasoning; Nebul exposes no caller control for it (its docs cover -# reasoning_effort only): https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning +# Efforts mirror Nebul's documented host-wide reasoning_effort enum (its docs +# note the effort-to-budget mapping is model-specific guidance): +# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning. Same-surface +# peers expose a toggle only, with no effort values to intersect. base_model = "nvidia/nemotron-3-nano-30b-a3b" -reasoning_options = [] - [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "max"] + [cost] input = 1 output = 3 diff --git a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml index abead25f50b..7fca9c970dd 100644 --- a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml +++ b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml @@ -1,12 +1,16 @@ -# Hybrid reasoning; Nebul exposes no caller control for it (its docs cover -# reasoning_effort only): https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning +# Efforts mirror Nebul's documented host-wide reasoning_effort enum (its docs +# note the effort-to-budget mapping is model-specific guidance): +# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning. Same-surface +# peers expose a toggle only, with no effort values to intersect. base_model = "nvidia/nemotron-3-super-120b-a12b" -reasoning_options = [] - [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "max"] + [cost] input = 0.32 output = 0.69 diff --git a/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml b/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml index 94094ee7c95..5c9dad1974a 100644 --- a/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml +++ b/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml @@ -1,12 +1,16 @@ -# Hybrid reasoning; Nebul exposes no caller control for it (its docs cover -# reasoning_effort only): https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning +# Efforts mirror Nebul's documented host-wide reasoning_effort enum (its docs +# note the effort-to-budget mapping is model-specific guidance): +# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning. Same-surface +# peers expose a toggle only, with no effort values to intersect. base_model = "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning" -reasoning_options = [] - [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "max"] + [cost] input = 0.16 output = 1.05 From 28b97e389b9cd2eaa0fd77f6e32dd8c76316902b Mon Sep 17 00:00:00 2001 From: Wynand Huizinga Date: Fri, 11 Sep 2026 20:23:31 +0200 Subject: [PATCH 13/13] fix(nebul): restrict reasoning_options to /model/info-advertised efforts --- .../nebul/models/Qwen/Qwen3.5-397B-A17B.toml | 15 +++++++-------- providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml | 15 +++++++-------- providers/nebul/models/google/gemma-4-31B-it.toml | 15 +++++++-------- .../NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml | 15 +++++++-------- .../NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml | 15 +++++++-------- ...emotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml | 15 +++++++-------- providers/nebul/models/zai-org/GLM-5.3.toml | 12 +++++++----- 7 files changed, 49 insertions(+), 53 deletions(-) diff --git a/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml b/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml index d6f8fe1901d..009e54c19fd 100644 --- a/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml +++ b/providers/nebul/models/Qwen/Qwen3.5-397B-A17B.toml @@ -1,16 +1,15 @@ -# Efforts mirror Nebul's documented host-wide reasoning_effort enum (its docs -# note the effort-to-budget mapping is model-specific guidance): -# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning. Same-surface -# peers expose a thinking toggle only, with no effort values to intersect. +# Reasoning control per Nebul's docs: GET /v1/model/info `reasoning_efforts` is the +# per-model source of truth for "the reasoning_effort values each model meaningfully +# accepts" (https://docs.nebul.io/docs/inference-api/models/model-catalog). It +# advertises none for this model, and the Chat Completions API documents no on/off +# toggle (https://docs.nebul.io/docs/inference-api/models/chat-completions), so this +# host exposes no caller control here. base_model = "alibaba/qwen3.5-397b-a17b" +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "effort" -values = ["low", "medium", "high", "max"] - [cost] input = 0.63 output = 3.78 diff --git a/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml b/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml index 6459efe54b7..e8efa43fbf8 100644 --- a/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml +++ b/providers/nebul/models/Qwen/Qwen3.8-27B-FP8.toml @@ -1,16 +1,15 @@ -# Effort: reasoning_effort (Nebul docs: accepted for reasoning-capable models, ignored -# only by non-reasoning models). Set is OpenRouter's low|medium|xhigh plus Nebul's -# documented high; xhigh is undocumented on Nebul and no peer advertises max. -# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning +# Reasoning control per Nebul's docs: GET /v1/model/info `reasoning_efforts` is the +# per-model source of truth for "the reasoning_effort values each model meaningfully +# accepts" (https://docs.nebul.io/docs/inference-api/models/model-catalog). It +# advertises none for this model, and the Chat Completions API documents no on/off +# toggle (https://docs.nebul.io/docs/inference-api/models/chat-completions), so this +# host exposes no caller control here. base_model = "alibaba/qwen3.8-27b" +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "effort" -values = ["low", "medium", "high"] - [cost] input = 0.21 output = 0.74 diff --git a/providers/nebul/models/google/gemma-4-31B-it.toml b/providers/nebul/models/google/gemma-4-31B-it.toml index f7ca96e731a..5accf868656 100644 --- a/providers/nebul/models/google/gemma-4-31B-it.toml +++ b/providers/nebul/models/google/gemma-4-31B-it.toml @@ -1,16 +1,15 @@ -# Efforts mirror Nebul's documented host-wide reasoning_effort enum (its docs -# note the effort-to-budget mapping is model-specific guidance): -# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning. Same-surface -# peers expose a toggle only, with no effort values to intersect. +# Reasoning control per Nebul's docs: GET /v1/model/info `reasoning_efforts` is the +# per-model source of truth for "the reasoning_effort values each model meaningfully +# accepts" (https://docs.nebul.io/docs/inference-api/models/model-catalog). It +# advertises none for this model, and the Chat Completions API documents no on/off +# toggle (https://docs.nebul.io/docs/inference-api/models/chat-completions), so this +# host exposes no caller control here. # Traces arrive inline as inside the content (Nebul docs), so interleaved = true base_model = "google/gemma-4-31b-it" +reasoning_options = [] interleaved = true -[[reasoning_options]] -type = "effort" -values = ["low", "medium", "high", "max"] - [cost] input = 0.42 output = 1.05 diff --git a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml index 4c22765a143..0b45ea1bb92 100644 --- a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml +++ b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16.toml @@ -1,16 +1,15 @@ -# Efforts mirror Nebul's documented host-wide reasoning_effort enum (its docs -# note the effort-to-budget mapping is model-specific guidance): -# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning. Same-surface -# peers expose a toggle only, with no effort values to intersect. +# Reasoning control per Nebul's docs: GET /v1/model/info `reasoning_efforts` is the +# per-model source of truth for "the reasoning_effort values each model meaningfully +# accepts" (https://docs.nebul.io/docs/inference-api/models/model-catalog). It +# advertises none for this model, and the Chat Completions API documents no on/off +# toggle (https://docs.nebul.io/docs/inference-api/models/chat-completions), so this +# host exposes no caller control here. base_model = "nvidia/nemotron-3-nano-30b-a3b" +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "effort" -values = ["low", "medium", "high", "max"] - [cost] input = 1 output = 3 diff --git a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml index 7fca9c970dd..e8bd3c054dc 100644 --- a/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml +++ b/providers/nebul/models/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16.toml @@ -1,16 +1,15 @@ -# Efforts mirror Nebul's documented host-wide reasoning_effort enum (its docs -# note the effort-to-budget mapping is model-specific guidance): -# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning. Same-surface -# peers expose a toggle only, with no effort values to intersect. +# Reasoning control per Nebul's docs: GET /v1/model/info `reasoning_efforts` is the +# per-model source of truth for "the reasoning_effort values each model meaningfully +# accepts" (https://docs.nebul.io/docs/inference-api/models/model-catalog). It +# advertises none for this model, and the Chat Completions API documents no on/off +# toggle (https://docs.nebul.io/docs/inference-api/models/chat-completions), so this +# host exposes no caller control here. base_model = "nvidia/nemotron-3-super-120b-a12b" +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "effort" -values = ["low", "medium", "high", "max"] - [cost] input = 0.32 output = 0.69 diff --git a/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml b/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml index 5c9dad1974a..943ff62c877 100644 --- a/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml +++ b/providers/nebul/models/nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-FP8.toml @@ -1,16 +1,15 @@ -# Efforts mirror Nebul's documented host-wide reasoning_effort enum (its docs -# note the effort-to-budget mapping is model-specific guidance): -# https://docs.nebul.io/docs/inference-api/advanced-topics/reasoning. Same-surface -# peers expose a toggle only, with no effort values to intersect. +# Reasoning control per Nebul's docs: GET /v1/model/info `reasoning_efforts` is the +# per-model source of truth for "the reasoning_effort values each model meaningfully +# accepts" (https://docs.nebul.io/docs/inference-api/models/model-catalog). It +# advertises none for this model, and the Chat Completions API documents no on/off +# toggle (https://docs.nebul.io/docs/inference-api/models/chat-completions), so this +# host exposes no caller control here. base_model = "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning" +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "effort" -values = ["low", "medium", "high", "max"] - [cost] input = 0.16 output = 1.05 diff --git a/providers/nebul/models/zai-org/GLM-5.3.toml b/providers/nebul/models/zai-org/GLM-5.3.toml index 0a964c0dda7..09dfa0df440 100644 --- a/providers/nebul/models/zai-org/GLM-5.3.toml +++ b/providers/nebul/models/zai-org/GLM-5.3.toml @@ -1,13 +1,15 @@ -# GLM-5.3 always reasons; efforts low|high|max per zhipuai's official entry +# Reasoning control per Nebul's docs: GET /v1/model/info `reasoning_efforts` is the +# per-model source of truth for "the reasoning_effort values each model meaningfully +# accepts" (https://docs.nebul.io/docs/inference-api/models/model-catalog). It +# advertises an empty list for this model, and the Chat Completions API documents no +# on/off toggle (https://docs.nebul.io/docs/inference-api/models/chat-completions), +# so this host exposes no caller control here. # Traces arrive in message.reasoning (Nebul docs), so interleaved = true base_model = "zhipuai/glm-5.3" +reasoning_options = [] interleaved = true -[[reasoning_options]] -type = "effort" -values = ["low", "high", "max"] - [cost] input = 1.47 output = 4.62