diff --git a/.github/workflows/sync-models.yml b/.github/workflows/sync-models.yml index c46c20519a..8f5434f7c0 100644 --- a/.github/workflows/sync-models.yml +++ b/.github/workflows/sync-models.yml @@ -65,6 +65,7 @@ jobs: env: BASETEN_API_KEY: ${{ secrets.BASETEN_API_KEY }} HF_TOKEN: ${{ secrets.HF_TOKEN }} + NEBIUS_API_KEY: ${{ secrets.NEBIUS_API_KEY }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} VENICE_API_KEY: ${{ secrets.VENICE_API_KEY }} LLMGATEWAY_API_KEY: ${{ secrets.LLMGATEWAY_API_KEY }} diff --git a/packages/core/src/sync/index.ts b/packages/core/src/sync/index.ts index 3229b15679..8343af3d51 100644 --- a/packages/core/src/sync/index.ts +++ b/packages/core/src/sync/index.ts @@ -10,6 +10,7 @@ import { cloudflareWorkersAi } from "./providers/cloudflare-workers-ai.js"; import { google } from "./providers/google.js"; import { huggingface } from "./providers/huggingface.js"; import { llmgateway } from "./providers/llmgateway.js"; +import { nebius } from "./providers/nebius.js"; import { openrouter } from "./providers/openrouter.js"; import { ovhcloud } from "./providers/ovhcloud.js"; import { vercel } from "./providers/vercel.js"; @@ -86,6 +87,7 @@ export const providers: { google: SyncProvider; huggingface: SyncProvider; llmgateway: SyncProvider; + nebius: SyncProvider; openrouter: SyncProvider; ovhcloud: SyncProvider; vercel: SyncProvider; @@ -98,6 +100,7 @@ export const providers: { google, huggingface, llmgateway, + nebius, openrouter, ovhcloud, vercel, @@ -108,7 +111,7 @@ export const providers: { export const groups = { aggregators: ["huggingface", "llmgateway", "openrouter", "vercel"], cloudflare: ["cloudflare-workers-ai"], - direct: ["baseten", "chutes", "google", "ovhcloud", "venice", "xai"], + direct: ["baseten", "chutes", "google", "nebius", "ovhcloud", "venice", "xai"], } as const; type ProviderID = keyof typeof providers; diff --git a/packages/core/src/sync/providers/nebius.ts b/packages/core/src/sync/providers/nebius.ts new file mode 100644 index 0000000000..c10a69f19e --- /dev/null +++ b/packages/core/src/sync/providers/nebius.ts @@ -0,0 +1,262 @@ +import { readdirSync } from "node:fs"; +import path from "node:path"; +import { z } from "zod"; + +import { inferKimiFamily, ModelFamilyValues } from "../../family.js"; +import type { ExistingModel, SyncProvider, SyncedFullModel, SyncedModel } from "../index.js"; +import { factorBaseModel } from "./openrouter.js"; + +const API_ENDPOINT = "https://api.tokenfactory.nebius.com/v1/models"; +const MODELS_DIR = path.join(import.meta.dirname, "..", "..", "..", "..", "..", "models"); + +// Scoped to the orgs Nebius Token Factory currently hosts; extend as new orgs show up. +// Maps a Nebius org prefix to the provider-agnostic namespace under `models/`. +const NEBIUS_ORG_TO_MODEL_PROVIDER: Record = { + "meta-llama": "meta", + Qwen: "alibaba", + google: "google", + openai: "openai", + nvidia: "nvidia", + "zai-org": "zhipuai", + moonshotai: "moonshotai", + MiniMaxAI: "minimax", + "deepseek-ai": "deepseek", +}; + +const modelMetadataFilesByProvider = new Map>(); + +const NebiusPricing = z.object({ + prompt: z.string().optional(), + completion: z.string().optional(), +}).passthrough(); + +const NebiusArchitecture = z.object({ + modality: z.string(), + tokenizer: z.string().optional(), +}).passthrough(); + +export const NebiusModel = z.object({ + id: z.string(), + name: z.string().optional(), + created: z.number().optional(), + context_length: z.number().optional(), + architecture: NebiusArchitecture.optional(), + pricing: NebiusPricing.optional(), + supported_features: z.array(z.string()).optional(), + supported_sampling_parameters: z.array(z.string()).optional(), +}).passthrough(); + +export const NebiusResponse = z.object({ + data: z.array(NebiusModel), +}).passthrough(); + +export type NebiusModel = z.infer; + +type Modality = "text" | "audio" | "image" | "video" | "pdf"; + +const KNOWN_MODALITIES = new Set(["text", "audio", "image", "video", "pdf"]); + +export const nebius = { + id: "nebius", + name: "Nebius Token Factory", + modelsDir: "providers/nebius/models", + async fetchModels() { + const key = process.env.NEBIUS_API_KEY; + if (key === undefined) throw new Error("Nebius sync requires NEBIUS_API_KEY"); + + const url = new URL(API_ENDPOINT); + // `verbose=true` is required for pricing, context length, modality, and feature + // metadata; the default response only includes bare model IDs. + url.searchParams.set("verbose", "true"); + + const response = await fetch(url, { + headers: { Authorization: `Bearer ${key}` }, + }); + if (!response.ok) { + throw new Error(`Nebius models request failed: ${response.status} ${response.statusText}`); + } + return response.json(); + }, + parseModels(raw) { + return NebiusResponse.parse(raw).data; + }, + translateModel(model, context) { + return { + id: model.id, + model: buildNebiusModel(model, context.existing(model.id)), + }; + }, +} satisfies SyncProvider; + +export function buildNebiusModel( + model: NebiusModel, + existing: ExistingModel | undefined, + today = new Date().toISOString().slice(0, 10), +): SyncedModel { + const features = new Set(model.supported_features ?? []); + const samplingParameters = new Set(model.supported_sampling_parameters ?? []); + const { input, output } = parseModality(model.architecture?.modality, existing?.modalities); + const name = modelName(model, existing); + const attachment = input.some((value) => value !== "text"); + const reasoning = features.has("reasoning"); + const toolCall = features.has("tools"); + const structuredOutput = features.has("structured_outputs"); + // Nebius omits `supported_sampling_parameters` entirely for some model families; + // an empty list is ambiguous, so fall back to the existing flag in that case. + const temperature = samplingParameters.size > 0 + ? samplingParameters.has("temperature") + : existing?.temperature ?? true; + const context = model.context_length ?? existing?.limit?.context ?? 0; + const limit = { + context, + input: existing?.limit?.input, + output: existing?.limit?.output, + }; + const releaseDate = existing?.release_date ?? today; + const canonical = existing?.base_model ?? resolveCanonicalBaseModel(model.id); + + if (canonical !== undefined) { + return factorBaseModel( + canonical, + { + name: existing === undefined ? name : undefined, + attachment, + reasoning, + // The API only exposes a boolean `reasoning` capability, not the toggle/effort + // mechanism, so any reasoning_options are curated by hand and preserved as-is. + reasoning_options: existing?.reasoning_options, + temperature, + tool_call: toolCall, + structured_output: structuredOutput, + status: existing?.status, + interleaved: existing?.interleaved, + limit, + modalities: { input, output }, + cost: cost(model, existing), + }, + limit, + existing?.base_model === canonical ? existing.base_model_omit : undefined, + ); + } + + return { + name, + family: existing?.family ?? inferFamily(model.id, name), + release_date: releaseDate, + last_updated: existing?.last_updated ?? today, + attachment, + reasoning, + reasoning_options: existing?.reasoning_options, + temperature, + tool_call: toolCall, + structured_output: structuredOutput, + knowledge: existing?.knowledge, + open_weights: existing?.open_weights ?? true, + status: existing?.status, + interleaved: existing?.interleaved, + cost: cost(model, existing), + limit: { + input: limit.input, + context: limit.context, + // No authoritative output limit is available without canonical metadata; + // fall back to the served context length, same as OpenRouter's convention. + output: limit.output ?? context, + }, + modalities: { input, output }, + } satisfies SyncedFullModel; +} + +function modelName(model: NebiusModel, existing: ExistingModel | undefined) { + // Nebius occasionally bakes the org prefix into `name` (e.g. "openbmb/MiniCPM-V-4_5"); + // strip any such prefix rather than let it leak into the catalog. + const raw = model.name ?? existing?.name ?? model.id; + return raw.split("/").at(-1) ?? raw; +} + +function price(value: string | undefined) { + if (value === undefined) return undefined; + const number = Number(value); + return Number.isFinite(number) && number >= 0 + ? Math.round(number * 1_000_000_000_000) / 1_000_000 + : undefined; +} + +function cost(model: NebiusModel, existing: ExistingModel | undefined) { + const input = price(model.pricing?.prompt); + const output = price(model.pricing?.completion); + if (input === undefined || output === undefined) return existing?.cost; + + return { + input, + output, + // Cache pricing is not exposed by the Models API; existing ratios are curated by hand. + reasoning: existing?.cost?.reasoning, + cache_read: existing?.cost?.cache_read, + cache_write: existing?.cost?.cache_write, + tiers: existing?.cost?.tiers, + }; +} + +function parseModality( + modality: string | undefined, + fallback: ExistingModel["modalities"], +) { + const [rawInput, rawOutput] = (modality ?? "text->text").split("->"); + return { + input: parseModalityPart(rawInput, fallback?.input ?? ["text"]), + output: parseModalityPart(rawOutput, fallback?.output ?? ["text"]), + }; +} + +function parseModalityPart(part: string | undefined, fallback: Modality[]): Modality[] { + const values = (part ?? "") + .split("+") + .map((value) => value.trim().toLowerCase()) + // Nebius reports embedding models as `text->embedding`; the catalog schema has no + // embedding modality, and embedding models are catalogued as text-in/text-out. + .map((value) => (value === "embedding" ? "text" : value)) + .filter((value): value is Modality => KNOWN_MODALITIES.has(value as Modality)); + + return [...new Set(values.length > 0 ? values : fallback)]; +} + +function inferFamily(id: string, name: string) { + const kimiFamily = inferKimiFamily(id, name); + if (kimiFamily !== undefined) return kimiFamily; + + const target = `${id} ${name}`.toLowerCase(); + return [...ModelFamilyValues] + .sort((a, b) => b.length - a.length) + .find((family) => { + const value = family.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + if (family === "o") return new RegExp(`(^|[^a-z0-9])${value}(?=\\d|$|[^a-z0-9])`).test(target); + return new RegExp(`(^|[^a-z0-9])${value}(?=$|[^a-z0-9])`).test(target); + }); +} + +export function resolveCanonicalBaseModel(nebiusID: string): string | undefined { + const [org, ...modelParts] = nebiusID.split("/"); + if (org === undefined || modelParts.length === 0) return undefined; + + const provider = NEBIUS_ORG_TO_MODEL_PROVIDER[org]; + if (provider === undefined) return undefined; + + const modelID = modelParts.join("/"); + const candidates = [...new Set([modelID, modelID.toLowerCase()])]; + const match = candidates.find((candidate) => modelMetadataExists(provider, candidate)); + + return match === undefined ? undefined : `${provider}/${match}`; +} + +function modelMetadataExists(provider: string, modelID: string) { + let files = modelMetadataFilesByProvider.get(provider); + if (files === undefined) { + try { + files = new Set(readdirSync(path.join(MODELS_DIR, provider))); + } catch { + files = new Set(); + } + modelMetadataFilesByProvider.set(provider, files); + } + return files.has(`${modelID}.toml`); +} diff --git a/providers/nebius/models/MiniMaxAI/MiniMax-M2.5-fast.toml b/providers/nebius/models/MiniMaxAI/MiniMax-M2.5-fast.toml deleted file mode 100644 index e4d44d35b6..0000000000 --- a/providers/nebius/models/MiniMaxAI/MiniMax-M2.5-fast.toml +++ /dev/null @@ -1,27 +0,0 @@ -name = "MiniMax-M2.5-fast" -attachment = false -reasoning = true -reasoning_options = [] -status = "deprecated" -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-01" -release_date = "2025-01-20" -last_updated = "2026-05-07" -open_weights = true - -[cost] -input = 0.30 -output = 1.20 -cache_read = 0.03 -cache_write = 0.375 - -[limit] -context = 8_000 -input = 7_000 -output = 8_192 - -[modalities] -input = ["text"] -output = ["text"] diff --git a/providers/nebius/models/MiniMaxAI/MiniMax-M2.5.toml b/providers/nebius/models/MiniMaxAI/MiniMax-M2.5.toml index 448e398d94..59d1616537 100644 --- a/providers/nebius/models/MiniMaxAI/MiniMax-M2.5.toml +++ b/providers/nebius/models/MiniMaxAI/MiniMax-M2.5.toml @@ -1,26 +1,14 @@ -name = "MiniMax-M2.5" -attachment = false -reasoning = true +base_model = "minimax/MiniMax-M2.5" +structured_output = false reasoning_options = [] -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-01" -release_date = "2025-01-20" -last_updated = "2026-05-07" -open_weights = true [cost] -input = 0.30 -output = 1.20 +input = 0.3 +output = 1.2 cache_read = 0.03 cache_write = 0.375 [limit] -context = 196_608 +context = 8_000 input = 190_000 output = 8_192 - -[modalities] -input = ["text"] -output = ["text"] diff --git a/providers/nebius/models/NousResearch/Hermes-4-405B.toml b/providers/nebius/models/NousResearch/Hermes-4-405B.toml index 61d1275cd9..6fe70b3c4b 100644 --- a/providers/nebius/models/NousResearch/Hermes-4-405B.toml +++ b/providers/nebius/models/NousResearch/Hermes-4-405B.toml @@ -1,30 +1,31 @@ name = "Hermes-4-405B" +family = "nousresearch" +release_date = "2026-01-30" +last_updated = "2026-02-04" attachment = false reasoning = true -reasoning_options = [] +temperature = true tool_call = true structured_output = true -temperature = true knowledge = "2025-11" -release_date = "2026-01-30" -last_updated = "2026-02-04" open_weights = true +reasoning_options = [] + +[interleaved] +field = "reasoning_content" [cost] -input = 1.00 -output = 3.00 -reasoning = 3.00 -cache_read = 0.10 +input = 1 +output = 3 +reasoning = 3 +cache_read = 0.1 cache_write = 1.25 [limit] -context = 128_000 +context = 131_072 input = 120_000 output = 8_192 [modalities] input = ["text"] output = ["text"] - -[interleaved] -field = "reasoning_content" diff --git a/providers/nebius/models/NousResearch/Hermes-4-70B.toml b/providers/nebius/models/NousResearch/Hermes-4-70B.toml index 673a4fdce7..849c6aab06 100644 --- a/providers/nebius/models/NousResearch/Hermes-4-70B.toml +++ b/providers/nebius/models/NousResearch/Hermes-4-70B.toml @@ -1,30 +1,31 @@ name = "Hermes-4-70B" +family = "nousresearch" +release_date = "2026-01-30" +last_updated = "2026-02-04" attachment = false reasoning = true -reasoning_options = [] +temperature = true tool_call = true structured_output = true -temperature = true knowledge = "2025-11" -release_date = "2026-01-30" -last_updated = "2026-02-04" open_weights = true +reasoning_options = [] + +[interleaved] +field = "reasoning_content" [cost] input = 0.13 -output = 0.40 -reasoning = 0.40 +output = 0.4 +reasoning = 0.4 cache_read = 0.013 cache_write = 0.16 [limit] -context = 128_000 +context = 131_072 input = 120_000 output = 8_192 [modalities] input = ["text"] output = ["text"] - -[interleaved] -field = "reasoning_content" diff --git a/providers/nebius/models/PrimeIntellect/INTELLECT-3.toml b/providers/nebius/models/PrimeIntellect/INTELLECT-3.toml deleted file mode 100644 index d2de6f510b..0000000000 --- a/providers/nebius/models/PrimeIntellect/INTELLECT-3.toml +++ /dev/null @@ -1,26 +0,0 @@ -name = "INTELLECT-3" -attachment = false -reasoning = false -status = "deprecated" -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-10" -release_date = "2026-01-25" -last_updated = "2026-02-04" -open_weights = true - -[cost] -input = 0.20 -output = 1.10 -cache_read = 0.02 -cache_write = 0.25 - -[limit] -context = 128_000 -input = 120_000 -output = 8_192 - -[modalities] -input = ["text"] -output = ["text"] diff --git a/providers/nebius/models/Qwen/Qwen2.5-VL-72B-Instruct.toml b/providers/nebius/models/Qwen/Qwen2.5-VL-72B-Instruct.toml index b21f2c926d..cbe13a9bb1 100644 --- a/providers/nebius/models/Qwen/Qwen2.5-VL-72B-Instruct.toml +++ b/providers/nebius/models/Qwen/Qwen2.5-VL-72B-Instruct.toml @@ -1,12 +1,13 @@ name = "Qwen2.5-VL-72B-Instruct" +family = "qwen" +release_date = "2025-01-20" +last_updated = "2026-02-04" attachment = true reasoning = false -tool_call = true -structured_output = true temperature = true +tool_call = false +structured_output = false knowledge = "2024-12" -release_date = "2025-01-20" -last_updated = "2026-02-04" open_weights = true [cost] @@ -16,10 +17,10 @@ cache_read = 0.025 cache_write = 0.31 [limit] -context = 128_000 +context = 32_000 input = 120_000 output = 8_192 [modalities] input = ["text", "image"] -output = ["text"] \ No newline at end of file +output = ["text"] diff --git a/providers/nebius/models/Qwen/Qwen3-235B-A22B-Instruct-2507.toml b/providers/nebius/models/Qwen/Qwen3-235B-A22B-Instruct-2507.toml index 7ced37cf17..7b5066c07c 100644 --- a/providers/nebius/models/Qwen/Qwen3-235B-A22B-Instruct-2507.toml +++ b/providers/nebius/models/Qwen/Qwen3-235B-A22B-Instruct-2507.toml @@ -1,21 +1,22 @@ -name = "Qwen3 235B A22B Instruct 2507" +name = "Qwen3-235B-A22B-Instruct-2507" family = "qwen" release_date = "2025-07-25" last_updated = "2025-10-04" attachment = false reasoning = false temperature = true -knowledge = "2025-07" tool_call = true +structured_output = true +knowledge = "2025-07" open_weights = false [cost] -input = 0.20 -output = 0.60 +input = 0.2 +output = 0.6 [limit] -context = 262144 -output = 8192 +context = 262_144 +output = 8_192 [modalities] input = ["text"] diff --git a/providers/nebius/models/Qwen/Qwen3-235B-A22B-Thinking-2507-fast.toml b/providers/nebius/models/Qwen/Qwen3-235B-A22B-Thinking-2507-fast.toml deleted file mode 100644 index 68fb846cd3..0000000000 --- a/providers/nebius/models/Qwen/Qwen3-235B-A22B-Thinking-2507-fast.toml +++ /dev/null @@ -1,27 +0,0 @@ -name = "Qwen3-235B-A22B-Thinking-2507-fast" -attachment = false -reasoning = true -reasoning_options = [] -status = "deprecated" -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-07" -release_date = "2025-07-25" -last_updated = "2026-05-07" -open_weights = true - -[cost] -input = 0.50 -output = 2.00 -cache_read = 0.05 -cache_write = 0.625 - -[limit] -context = 8_000 -input = 7_000 -output = 8_192 - -[modalities] -input = ["text"] -output = ["text"] diff --git a/providers/nebius/models/Qwen/Qwen3-30B-A3B-Instruct-2507.toml b/providers/nebius/models/Qwen/Qwen3-30B-A3B-Instruct-2507.toml index 96c2d3f369..94d05d85dd 100644 --- a/providers/nebius/models/Qwen/Qwen3-30B-A3B-Instruct-2507.toml +++ b/providers/nebius/models/Qwen/Qwen3-30B-A3B-Instruct-2507.toml @@ -1,25 +1,26 @@ name = "Qwen3-30B-A3B-Instruct-2507" +family = "qwen" +release_date = "2026-01-28" +last_updated = "2026-02-04" attachment = false reasoning = false +temperature = true tool_call = true structured_output = true -temperature = true knowledge = "2025-12" -release_date = "2026-01-28" -last_updated = "2026-02-04" open_weights = true [cost] -input = 0.10 -output = 0.30 +input = 0.1 +output = 0.3 cache_read = 0.01 cache_write = 0.125 [limit] -context = 128_000 +context = 262_144 input = 120_000 output = 8_192 [modalities] input = ["text"] -output = ["text"] \ No newline at end of file +output = ["text"] diff --git a/providers/nebius/models/Qwen/Qwen3-32B.toml b/providers/nebius/models/Qwen/Qwen3-32B.toml index 6c619f6e5b..e1f40bd311 100644 --- a/providers/nebius/models/Qwen/Qwen3-32B.toml +++ b/providers/nebius/models/Qwen/Qwen3-32B.toml @@ -1,25 +1,14 @@ -name = "Qwen3-32B" -attachment = false -reasoning = false -tool_call = true +base_model = "alibaba/qwen3-32b" structured_output = true -temperature = true -knowledge = "2025-12" -release_date = "2026-01-28" -last_updated = "2026-02-04" -open_weights = true +reasoning_options = [] [cost] -input = 0.10 -output = 0.30 +input = 0.1 +output = 0.3 cache_read = 0.01 cache_write = 0.125 [limit] -context = 128_000 +context = 40_960 input = 120_000 output = 8_192 - -[modalities] -input = ["text"] -output = ["text"] \ No newline at end of file diff --git a/providers/nebius/models/Qwen/Qwen3-Embedding-8B.toml b/providers/nebius/models/Qwen/Qwen3-Embedding-8B.toml index 205cee1bc9..234ac30a71 100644 --- a/providers/nebius/models/Qwen/Qwen3-Embedding-8B.toml +++ b/providers/nebius/models/Qwen/Qwen3-Embedding-8B.toml @@ -1,24 +1,24 @@ name = "Qwen3-Embedding-8B" family = "text-embedding" +release_date = "2026-01-10" +last_updated = "2026-02-04" attachment = false reasoning = false +temperature = false tool_call = false structured_output = false -temperature = false knowledge = "2025-10" -release_date = "2026-01-10" -last_updated = "2026-02-04" open_weights = true [cost] input = 0.01 -output = 0.00 +output = 0 [limit] -context = 32_768 +context = 40_960 input = 32_768 output = 0 [modalities] input = ["text"] -output = ["text"] \ No newline at end of file +output = ["text"] diff --git a/providers/nebius/models/Qwen/Qwen3-Next-80B-A3B-Thinking-fast.toml b/providers/nebius/models/Qwen/Qwen3-Next-80B-A3B-Thinking-fast.toml deleted file mode 100644 index 20b8b16cf6..0000000000 --- a/providers/nebius/models/Qwen/Qwen3-Next-80B-A3B-Thinking-fast.toml +++ /dev/null @@ -1,27 +0,0 @@ -name = "Qwen3-Next-80B-A3B-Thinking-fast" -attachment = false -reasoning = true -reasoning_options = [] -status = "deprecated" -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-07" -release_date = "2025-07-25" -last_updated = "2026-05-07" -open_weights = true - -[cost] -input = 0.15 -output = 1.20 -cache_read = 0.015 -cache_write = 0.1875 - -[limit] -context = 8_000 -input = 7_000 -output = 8_192 - -[modalities] -input = ["text"] -output = ["text"] diff --git a/providers/nebius/models/Qwen/Qwen3-Next-80B-A3B-Thinking.toml b/providers/nebius/models/Qwen/Qwen3-Next-80B-A3B-Thinking.toml index 926f404dfb..3d0be3a65b 100644 --- a/providers/nebius/models/Qwen/Qwen3-Next-80B-A3B-Thinking.toml +++ b/providers/nebius/models/Qwen/Qwen3-Next-80B-A3B-Thinking.toml @@ -1,30 +1,18 @@ -name = "Qwen3-Next-80B-A3B-Thinking" -attachment = false -reasoning = true -reasoning_options = [] -tool_call = true +base_model = "alibaba/qwen3-next-80b-a3b-thinking" structured_output = true -temperature = true -knowledge = "2025-12" -release_date = "2026-01-28" -last_updated = "2026-02-04" -open_weights = true +reasoning_options = [] + +[interleaved] +field = "reasoning_content" [cost] input = 0.15 -output = 1.20 -reasoning = 1.20 +output = 1.2 +reasoning = 1.2 cache_read = 0.015 cache_write = 0.18 [limit] -context = 128_000 +context = 8_000 input = 120_000 output = 16_384 - -[modalities] -input = ["text"] -output = ["text"] - -[interleaved] -field = "reasoning_content" diff --git a/providers/nebius/models/Qwen/Qwen3.5-397B-A17B-fast.toml b/providers/nebius/models/Qwen/Qwen3.5-397B-A17B-fast.toml deleted file mode 100644 index 0bb3885a9f..0000000000 --- a/providers/nebius/models/Qwen/Qwen3.5-397B-A17B-fast.toml +++ /dev/null @@ -1,27 +0,0 @@ -name = "Qwen3.5-397B-A17B-fast" -attachment = false -reasoning = true -reasoning_options = [{ type = "toggle" }] -status = "deprecated" -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-07" -release_date = "2025-07-15" -last_updated = "2026-05-07" -open_weights = true - -[cost] -input = 0.60 -output = 3.60 -cache_read = 0.06 -cache_write = 0.75 - -[limit] -context = 8_000 -input = 7_000 -output = 8_192 - -[modalities] -input = ["text"] -output = ["text"] diff --git a/providers/nebius/models/Qwen/Qwen3.5-397B-A17B.toml b/providers/nebius/models/Qwen/Qwen3.5-397B-A17B.toml index d007e174e8..2cb9100fc3 100644 --- a/providers/nebius/models/Qwen/Qwen3.5-397B-A17B.toml +++ b/providers/nebius/models/Qwen/Qwen3.5-397B-A17B.toml @@ -1,26 +1,19 @@ -name = "Qwen3.5-397B-A17B" +base_model = "alibaba/qwen3.5-397b-a17b" attachment = false -reasoning = true -reasoning_options = [{ type = "toggle" }] -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-07" -release_date = "2025-07-15" -last_updated = "2026-05-07" -open_weights = true + +[[reasoning_options]] +type = "toggle" [cost] -input = 0.60 -output = 3.60 +input = 0.6 +output = 3.6 cache_read = 0.06 cache_write = 0.75 [limit] -context = 262_144 +context = 8_000 input = 250_000 output = 8_192 [modalities] input = ["text"] -output = ["text"] diff --git a/providers/nebius/models/deepseek-ai/DeepSeek-V3.2-fast.toml b/providers/nebius/models/deepseek-ai/DeepSeek-V3.2-fast.toml deleted file mode 100644 index a5679059e0..0000000000 --- a/providers/nebius/models/deepseek-ai/DeepSeek-V3.2-fast.toml +++ /dev/null @@ -1,27 +0,0 @@ -name = "DeepSeek-V3.2-fast" -attachment = false -reasoning = true -reasoning_options = [{ type = "toggle" }] -status = "deprecated" -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-01" -release_date = "2025-01-27" -last_updated = "2026-05-07" -open_weights = true - -[cost] -input = 0.40 -output = 2.00 -cache_read = 0.04 -cache_write = 0.5 - -[limit] -context = 8_000 -input = 7_000 -output = 8_192 - -[modalities] -input = ["text"] -output = ["text"] diff --git a/providers/nebius/models/deepseek-ai/DeepSeek-V3.2.toml b/providers/nebius/models/deepseek-ai/DeepSeek-V3.2.toml deleted file mode 100644 index e0dd51c804..0000000000 --- a/providers/nebius/models/deepseek-ai/DeepSeek-V3.2.toml +++ /dev/null @@ -1,31 +0,0 @@ -name = "DeepSeek-V3.2" -attachment = false -reasoning = true -reasoning_options = [{ type = "toggle" }] -status = "deprecated" -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-11" -release_date = "2026-01-20" -last_updated = "2026-02-04" -open_weights = true - -[cost] -input = 0.30 -output = 0.45 -reasoning = 0.45 -cache_read = 0.03 -cache_write = 0.375 - -[limit] -context = 163_000 -input = 160_000 -output = 16_384 - -[modalities] -input = ["text"] -output = ["text"] - -[interleaved] -field = "reasoning_content" diff --git a/providers/nebius/models/deepseek-ai/DeepSeek-V4-Pro.toml b/providers/nebius/models/deepseek-ai/DeepSeek-V4-Pro.toml index 948f4d822d..bd09a38c39 100644 --- a/providers/nebius/models/deepseek-ai/DeepSeek-V4-Pro.toml +++ b/providers/nebius/models/deepseek-ai/DeepSeek-V4-Pro.toml @@ -1,13 +1,19 @@ base_model = "deepseek/deepseek-v4-pro" -reasoning_options = [ - { type = "toggle" }, - { type = "effort", values = ["low", "medium", "high"] }, -] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "toggle" + +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high"] + [cost] input = 1.75 output = 3.5 cache_read = 0.15 + +[limit] +context = 1_048_576 diff --git a/providers/nebius/models/google/gemma-3-27b-it.toml b/providers/nebius/models/google/gemma-3-27b-it.toml index f0fc3dbdad..57ea6f279b 100644 --- a/providers/nebius/models/google/gemma-3-27b-it.toml +++ b/providers/nebius/models/google/gemma-3-27b-it.toml @@ -1,17 +1,18 @@ name = "Gemma-3-27b-it" -attachment = true +family = "gemma" +release_date = "2026-01-20" +last_updated = "2026-02-04" +attachment = false reasoning = false -tool_call = true -structured_output = true temperature = true +tool_call = true +structured_output = false knowledge = "2025-10" -release_date = "2026-01-20" -last_updated = "2026-02-04" open_weights = true [cost] -input = 0.10 -output = 0.30 +input = 0.1 +output = 0.3 cache_read = 0.01 cache_write = 0.125 @@ -21,5 +22,5 @@ input = 100_000 output = 8_192 [modalities] -input = ["text", "image"] -output = ["text"] \ No newline at end of file +input = ["text"] +output = ["text"] diff --git a/providers/nebius/models/meta-llama/Llama-3.3-70B-Instruct.toml b/providers/nebius/models/meta-llama/Llama-3.3-70B-Instruct.toml index 4644871f3d..f9838c63e5 100644 --- a/providers/nebius/models/meta-llama/Llama-3.3-70B-Instruct.toml +++ b/providers/nebius/models/meta-llama/Llama-3.3-70B-Instruct.toml @@ -1,25 +1,14 @@ -name = "Llama-3.3-70B-Instruct" +base_model = "meta/llama-3.3-70b-instruct" attachment = false -reasoning = false -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-08" -release_date = "2025-12-05" -last_updated = "2026-02-04" -open_weights = true +structured_output = false [cost] input = 0.13 -output = 0.40 +output = 0.4 cache_read = 0.013 cache_write = 0.16 [limit] -context = 128_000 +context = 131_072 input = 120_000 output = 8_192 - -[modalities] -input = ["text"] -output = ["text"] \ No newline at end of file diff --git a/providers/nebius/models/moonshotai/Kimi-K2.5-fast.toml b/providers/nebius/models/moonshotai/Kimi-K2.5-fast.toml deleted file mode 100644 index 83dd3e8848..0000000000 --- a/providers/nebius/models/moonshotai/Kimi-K2.5-fast.toml +++ /dev/null @@ -1,31 +0,0 @@ -name = "Kimi-K2.5-fast" -family = "kimi-k2" -release_date = "2025-12-15" -last_updated = "2026-02-04" -attachment = true -reasoning = true -reasoning_options = [{ type = "toggle" }] -status = "deprecated" -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-06" -open_weights = true - -[cost] -input = 0.50 -output = 2.50 -cache_read = 0.05 -cache_write = 0.625 - -[limit] -context = 256_000 -input = 256_000 -output = 8192 - -[modalities] -input = ["text", "image"] -output = ["text"] - -[interleaved] -field = "reasoning_content" diff --git a/providers/nebius/models/moonshotai/Kimi-K2.5.toml b/providers/nebius/models/moonshotai/Kimi-K2.5.toml deleted file mode 100644 index b274d3a0b1..0000000000 --- a/providers/nebius/models/moonshotai/Kimi-K2.5.toml +++ /dev/null @@ -1,32 +0,0 @@ -name = "Kimi-K2.5" -family = "kimi-k2" -release_date = "2025-12-15" -last_updated = "2026-02-04" -attachment = true -reasoning = true -reasoning_options = [{ type = "toggle" }] -status = "deprecated" -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-06" -open_weights = true - -[cost] -input = 0.50 -output = 2.50 -reasoning = 2.50 -cache_read = 0.05 -cache_write = 0.625 - -[limit] -context = 256_000 -input = 256_000 -output = 8192 - -[modalities] -input = ["text", "image"] -output = ["text"] - -[interleaved] -field = "reasoning_content" diff --git a/providers/nebius/models/moonshotai/Kimi-K2.6.toml b/providers/nebius/models/moonshotai/Kimi-K2.6.toml new file mode 100644 index 0000000000..d6a08cfc6f --- /dev/null +++ b/providers/nebius/models/moonshotai/Kimi-K2.6.toml @@ -0,0 +1,12 @@ +base_model = "moonshotai/kimi-k2.6" +reasoning_options = [] + +[cost] +input = 0.95 +output = 4 + +[limit] +context = 8_000 + +[modalities] +input = ["text", "image"] diff --git a/providers/nebius/models/moonshotai/Kimi-K2.7-Code.toml b/providers/nebius/models/moonshotai/Kimi-K2.7-Code.toml new file mode 100644 index 0000000000..49cacd2cba --- /dev/null +++ b/providers/nebius/models/moonshotai/Kimi-K2.7-Code.toml @@ -0,0 +1,11 @@ +base_model = "moonshotai/kimi-k2.7-code" +attachment = false +temperature = true +reasoning_options = [] + +[cost] +input = 0.95 +output = 4 + +[modalities] +input = ["text"] diff --git a/providers/nebius/models/nvidia/Cosmos3-Super-Reasoner.toml b/providers/nebius/models/nvidia/Cosmos3-Super-Reasoner.toml new file mode 100644 index 0000000000..71c31012eb --- /dev/null +++ b/providers/nebius/models/nvidia/Cosmos3-Super-Reasoner.toml @@ -0,0 +1,22 @@ +name = "Cosmos3-Super-Reasoner" +release_date = "2026-07-01" +last_updated = "2026-07-01" +attachment = true +reasoning = true +temperature = true +tool_call = true +structured_output = true +open_weights = true +reasoning_options = [] + +[cost] +input = 0.1 +output = 0.3 + +[limit] +context = 8_000 +output = 8_000 + +[modalities] +input = ["text", "image"] +output = ["text"] diff --git a/providers/nebius/models/nvidia/Llama-3_1-Nemotron-Ultra-253B-v1.toml b/providers/nebius/models/nvidia/Llama-3_1-Nemotron-Ultra-253B-v1.toml index 47b8177e2e..f6bab751b4 100644 --- a/providers/nebius/models/nvidia/Llama-3_1-Nemotron-Ultra-253B-v1.toml +++ b/providers/nebius/models/nvidia/Llama-3_1-Nemotron-Ultra-253B-v1.toml @@ -1,26 +1,15 @@ -name = "Llama-3.1-Nemotron-Ultra-253B-v1" base_model = "nvidia/llama-3.1-nemotron-ultra-253b" -attachment = false -reasoning = false -tool_call = true +tool_call = false structured_output = true -temperature = true -knowledge = "2024-12" -release_date = "2025-01-15" -last_updated = "2026-02-04" -open_weights = true +reasoning_options = [] [cost] -input = 0.60 -output = 1.80 +input = 0.6 +output = 1.8 cache_read = 0.06 cache_write = 0.75 [limit] -context = 128_000 +context = 8_000 input = 120_000 output = 4_096 - -[modalities] -input = ["text"] -output = ["text"] \ No newline at end of file diff --git a/providers/nebius/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B.toml b/providers/nebius/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B.toml index f24f5276fe..d27efb0668 100644 --- a/providers/nebius/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B.toml +++ b/providers/nebius/models/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B.toml @@ -1,14 +1,6 @@ -name = "Nemotron-3-Nano-30B-A3B" base_model = "nvidia/nemotron-3-nano-30b-a3b" -attachment = false -reasoning = false -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-05" -release_date = "2025-08-10" -last_updated = "2026-02-04" -open_weights = true +structured_output = false +reasoning_options = [] [cost] input = 0.06 @@ -17,10 +9,5 @@ cache_read = 0.006 cache_write = 0.075 [limit] -context = 32_000 input = 30_000 output = 4_096 - -[modalities] -input = ["text"] -output = ["text"] \ No newline at end of file diff --git a/providers/nebius/models/nvidia/Nemotron-3-Nano-Omni.toml b/providers/nebius/models/nvidia/Nemotron-3-Nano-Omni.toml index 114b15c208..b577986127 100644 --- a/providers/nebius/models/nvidia/Nemotron-3-Nano-Omni.toml +++ b/providers/nebius/models/nvidia/Nemotron-3-Nano-Omni.toml @@ -1,15 +1,7 @@ -name = "Nemotron-3-Nano-Omni" base_model = "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning" attachment = false -reasoning = true -reasoning_options = [] -tool_call = true structured_output = true -temperature = true -knowledge = "2025-01" -release_date = "2025-01-20" -last_updated = "2026-05-07" -open_weights = true +reasoning_options = [] [cost] input = 0.06 @@ -18,10 +10,9 @@ cache_read = 0.006 cache_write = 0.075 [limit] -context = 65_536 +context = 8_000 input = 60_000 output = 8_192 [modalities] input = ["text"] -output = ["text"] diff --git a/providers/nebius/models/nvidia/Nemotron-3-Ultra-550b-a55b.toml b/providers/nebius/models/nvidia/Nemotron-3-Ultra-550b-a55b.toml new file mode 100644 index 0000000000..d06f8a9cbc --- /dev/null +++ b/providers/nebius/models/nvidia/Nemotron-3-Ultra-550b-a55b.toml @@ -0,0 +1,10 @@ +base_model = "nvidia/nemotron-3-ultra-550b-a55b" +structured_output = true +reasoning_options = [] + +[cost] +input = 1 +output = 3 + +[limit] +context = 8_000 diff --git a/providers/nebius/models/nvidia/nemotron-3-super-120b-a12b.toml b/providers/nebius/models/nvidia/nemotron-3-super-120b-a12b.toml index ae14965ad6..f93bc83302 100644 --- a/providers/nebius/models/nvidia/nemotron-3-super-120b-a12b.toml +++ b/providers/nebius/models/nvidia/nemotron-3-super-120b-a12b.toml @@ -1,25 +1,11 @@ -name = "Nemotron-3-Super-120B-A12B" base_model = "nvidia/nemotron-3-super-120b-a12b" -attachment = false -reasoning = true -reasoning_options = [] -tool_call = true structured_output = true -temperature = true -knowledge = "2026-02" -release_date = "2026-03-11" -last_updated = "2026-03-12" -open_weights = true +reasoning_options = [] [cost] -input = 0.30 -output = 0.90 +input = 0.3 +output = 0.9 [limit] -context = 256_000 input = 256_000 output = 32_768 - -[modalities] -input = ["text"] -output = ["text"] diff --git a/providers/nebius/models/openai/gpt-oss-120b-fast.toml b/providers/nebius/models/openai/gpt-oss-120b-fast.toml deleted file mode 100644 index 5cebac08cc..0000000000 --- a/providers/nebius/models/openai/gpt-oss-120b-fast.toml +++ /dev/null @@ -1,27 +0,0 @@ -name = "gpt-oss-120b-fast" -attachment = false -reasoning = true -reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] -status = "deprecated" -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-06" -release_date = "2025-06-10" -last_updated = "2026-05-07" -open_weights = true - -[cost] -input = 0.10 -output = 0.50 -cache_read = 0.01 -cache_write = 0.125 - -[limit] -context = 8_000 -input = 7_000 -output = 8_192 - -[modalities] -input = ["text"] -output = ["text"] diff --git a/providers/nebius/models/openai/gpt-oss-120b.toml b/providers/nebius/models/openai/gpt-oss-120b.toml index c73b1027e3..76f50c79ac 100644 --- a/providers/nebius/models/openai/gpt-oss-120b.toml +++ b/providers/nebius/models/openai/gpt-oss-120b.toml @@ -1,30 +1,19 @@ -name = "gpt-oss-120b" -attachment = false -reasoning = true -reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] -tool_call = true -structured_output = true -temperature = true -knowledge = "2025-09" -release_date = "2026-01-10" -last_updated = "2026-02-04" -open_weights = true +base_model = "openai/gpt-oss-120b" + +[interleaved] +field = "reasoning_content" + +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high"] [cost] input = 0.15 -output = 0.60 -reasoning = 0.60 +output = 0.6 +reasoning = 0.6 cache_read = 0.015 cache_write = 0.18 [limit] -context = 128_000 input = 124_000 output = 8_192 - -[modalities] -input = ["text"] -output = ["text"] - -[interleaved] -field = "reasoning_content" diff --git a/providers/nebius/models/openbmb/MiniCPM-V-4_5.toml b/providers/nebius/models/openbmb/MiniCPM-V-4_5.toml new file mode 100644 index 0000000000..1932885092 --- /dev/null +++ b/providers/nebius/models/openbmb/MiniCPM-V-4_5.toml @@ -0,0 +1,21 @@ +name = "MiniCPM-V-4_5" +release_date = "2026-07-01" +last_updated = "2026-07-01" +attachment = true +reasoning = false +temperature = true +tool_call = false +structured_output = true +open_weights = true + +[cost] +input = 0.658 +output = 1.11 + +[limit] +context = 8_000 +output = 8_000 + +[modalities] +input = ["text", "image"] +output = ["text"] diff --git a/providers/nebius/models/zai-org/GLM-5.1.toml b/providers/nebius/models/zai-org/GLM-5.1.toml new file mode 100644 index 0000000000..978b632cc4 --- /dev/null +++ b/providers/nebius/models/zai-org/GLM-5.1.toml @@ -0,0 +1,9 @@ +base_model = "zhipuai/glm-5.1" +reasoning_options = [] + +[cost] +input = 1.4 +output = 4.4 + +[limit] +context = 202_752 diff --git a/providers/nebius/models/zai-org/GLM-5.2.toml b/providers/nebius/models/zai-org/GLM-5.2.toml index d50ba73852..0858ea7e39 100644 --- a/providers/nebius/models/zai-org/GLM-5.2.toml +++ b/providers/nebius/models/zai-org/GLM-5.2.toml @@ -1,16 +1,16 @@ base_model = "zhipuai/glm-5.2" +[interleaved] +field = "reasoning_content" + [[reasoning_options]] type = "effort" values = ["low", "medium", "high"] -[interleaved] -field = "reasoning_content" - [cost] input = 1.4 output = 4.4 [limit] -context = 432_000 +context = 1_048_576 output = 432_000 diff --git a/providers/nebius/models/zai-org/GLM-5.toml b/providers/nebius/models/zai-org/GLM-5.toml deleted file mode 100644 index 099a4056c0..0000000000 --- a/providers/nebius/models/zai-org/GLM-5.toml +++ /dev/null @@ -1,30 +0,0 @@ -name = "GLM-5" -attachment = false -reasoning = true -reasoning_options = [{ type = "toggle" }] -status = "deprecated" -tool_call = true -structured_output = true -temperature = true -knowledge = "2026-01" -release_date = "2026-03-01" -last_updated = "2026-03-10" -open_weights = false - -[cost] -input = 1.00 -output = 3.20 -cache_read = 0.10 -cache_write = 1.00 - -[limit] -context = 200_000 -input = 200_000 -output = 16_384 - -[modalities] -input = ["text"] -output = ["text"] - -[interleaved] -field = "reasoning_content" diff --git a/sync.md b/sync.md index c0e92693b0..db1d8ee119 100644 --- a/sync.md +++ b/sync.md @@ -93,7 +93,7 @@ Prefer small, provider-specific PRs when adding a provider. If the provider has ## Automation -`.github/workflows/sync-models.yml` runs on an hourly schedule and manually through `workflow_dispatch`. +`.github/workflows/sync-models.yml` runs on a daily schedule and manually through `workflow_dispatch`. The workflow: @@ -107,7 +107,7 @@ The workflow: Each provider job checks out `dev` and writes to a fixed provider branch like `automation/sync-models-openrouter`. If that provider's sync PR is already open, later scheduled runs force-update the same branch and edit the existing PR instead of creating another one. Provider jobs do not share unmerged changes with each other; OpenRouter only uses `base_model` for model metadata entries already present on `dev`. -CI automatically picks up providers registered in `providers` in `packages/core/src/sync/index.ts`. Adding a new sync provider there is enough to get an hourly provider-specific sync job, branch, labels, title, and PR naming convention. The workflow only needs manual updates when a new provider requires new secrets or other environment variables. +CI automatically picks up providers registered in `providers` in `packages/core/src/sync/index.ts`. Adding a new sync provider there is enough to get a daily provider-specific sync job, branch, labels, title, and PR naming convention. The workflow only needs manual updates when a new provider requires new secrets or other environment variables. Actions are pinned by commit SHA. Keep new workflow actions pinned the same way. @@ -155,6 +155,18 @@ xAI is implemented in `packages/core/src/sync/providers/xai.ts`. - Existing xAI models are updated from API-authoritative fields while local metadata is preserved for fields the API does not expose, especially output token limits and some feature/capability flags. - New xAI API models are reported in `.sync/model-sync-report.md` but not created automatically because the API does not provide enough authoritative metadata for complete catalog entries. +## Nebius Token Factory Notes + +Nebius Token Factory is implemented in `packages/core/src/sync/providers/nebius.ts`. + +- Source endpoint: `https://api.tokenfactory.nebius.com/v1/models?verbose=true`; the `verbose=true` query parameter is required, since the default response only returns bare model IDs with no pricing, context length, or capability metadata. +- Required auth: `NEBIUS_API_KEY`. +- Model IDs map directly to TOML paths under `providers/nebius/models` (e.g. `openai/gpt-oss-120b` → `providers/nebius/models/openai/gpt-oss-120b.toml`). +- The API is authoritative for `name` (a small `NEBIUS_ORG_TO_MODEL_PROVIDER`-independent fix strips org prefixes Nebius sometimes bakes into `name`, e.g. `openbmb/MiniCPM-V-4_5`), `cost` (converted from per-token to per-1M-token pricing), `limit.context`, `reasoning`/`tool_call`/`structured_output` (from `supported_features`), `temperature` (from `supported_sampling_parameters`, falling back to the existing flag when the list is empty), and `modalities`/`attachment` (parsed from `architecture.modality`, e.g. `text+image->text`; the `embedding` modality is mapped to `text` since the catalog schema has no embedding modality). +- New remote models are created automatically. A small `NEBIUS_ORG_TO_MODEL_PROVIDER` map (scoped to orgs Nebius currently hosts) resolves known orgs (`meta-llama`, `Qwen`, `google`, `openai`, `nvidia`, `zai-org`, `moonshotai`, `MiniMaxAI`, `deepseek-ai`) to their `models/` namespace, and `factorBaseModel` (shared with OpenRouter) writes a `base_model` reference when a matching canonical entry exists — this is how new models get an authoritative output token limit despite the Models API not exposing one. New models with no canonical `models/` match (unmapped orgs, or a checkpoint not yet in `models/`) are still created as full entries, with `limit.output` falling back to the served context length. +- `reasoning_options`, `limit.input`, `limit.output` (for already-factored/full existing entries), `knowledge`, `family`, `status`, `interleaved`, and cache pricing ratios are preserved from existing TOMLs since the API does not expose them. +- `base_model`/`base_model_omit` are preserved from existing TOMLs, and newly resolved for brand-new models per the point above. + ## OVHcloud Notes OVHcloud AI Endpoints is implemented in `packages/core/src/sync/providers/ovhcloud.ts`.