diff --git a/.github/workflows/sync-models.yml b/.github/workflows/sync-models.yml index 7edd032424..ebb4f04304 100644 --- a/.github/workflows/sync-models.yml +++ b/.github/workflows/sync-models.yml @@ -90,6 +90,7 @@ jobs: XAI_API_KEY: ${{ secrets.XAI_API_KEY }} CLOUDFLARE_WORKERS_AI_SYNC_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_WORKERS_AI_SYNC_ACCOUNT_ID }} CLOUDFLARE_WORKERS_AI_SYNC_API_TOKEN: ${{ secrets.CLOUDFLARE_WORKERS_AI_SYNC_API_TOKEN }} + TENSORX_API_KEY: ${{ secrets.TENSORX_API_KEY }} - name: Validate models run: bun validate diff --git a/packages/core/src/sync/index.ts b/packages/core/src/sync/index.ts index a3fa90270f..69069ae457 100644 --- a/packages/core/src/sync/index.ts +++ b/packages/core/src/sync/index.ts @@ -27,6 +27,7 @@ import { openrouter } from "./providers/openrouter.js"; import { ovhcloud } from "./providers/ovhcloud.js"; import { pioneer } from "./providers/pioneer.js"; import { requesty } from "./providers/requesty.js"; +import { tensorx } from "./providers/tensorx.js"; import { tinfoil } from "./providers/tinfoil.js"; import { vercel } from "./providers/vercel.js"; import { venice } from "./providers/venice.js"; @@ -133,6 +134,7 @@ export const providers: { ovhcloud: SyncProvider; pioneer: SyncProvider; requesty: SyncProvider; + tensorx: SyncProvider; tinfoil: SyncProvider; vercel: SyncProvider; venice: SyncProvider; @@ -161,6 +163,7 @@ export const providers: { ovhcloud, pioneer, requesty, + tensorx, tinfoil, vercel, venice, @@ -183,7 +186,7 @@ export const groups = { "vercel", ], cloudflare: ["cloudflare-workers-ai"], - direct: ["ambient", "anthropic", "baseten", "chutes", "deepinfra", "digitalocean", "google", "hyper", "openai", "ovhcloud", "pioneer", "tinfoil", "venice", "wandb", "xai"], + direct: ["ambient", "anthropic", "baseten", "chutes", "deepinfra", "digitalocean", "google", "hyper", "openai", "ovhcloud", "pioneer", "tensorx", "tinfoil", "venice", "wandb", "xai"], } as const; type ProviderID = keyof typeof providers; diff --git a/packages/core/src/sync/providers/tensorx.ts b/packages/core/src/sync/providers/tensorx.ts new file mode 100644 index 0000000000..a8b71dcd0e --- /dev/null +++ b/packages/core/src/sync/providers/tensorx.ts @@ -0,0 +1,208 @@ +import { z } from "zod"; + +import type { ExistingModel, SyncProvider, SyncedFullModel, SyncedModel } from "../index.js"; +import { factorBaseModel, resolveCanonicalBaseModel } from "./openrouter.js"; + +const API_ENDPOINT = "https://api.tensorx.ai/v1/model/info"; + +// `supported_openai_params` is a static LiteLLM-style list: every catalog entry +// advertises the same params, including `temperature` and `response_format` on +// the Whisper and embedding models. It carries no per-model signal, so nothing +// is derived from it — `temperature` and `structured_output` stay lab metadata. +// +// `supports_*` uses three values: true, false, and null for "not published". +// Only real booleans are authored; null leaves the field to the base model. +const TensorXModelInfo = z.object({ + mode: z.string().nullish(), + max_input_tokens: z.number().int().nonnegative().nullish(), + max_output_tokens: z.number().int().nonnegative().nullish(), + max_tokens: z.number().int().nonnegative().nullish(), + supports_reasoning: z.boolean().nullish(), + supports_tool_choice: z.boolean().nullish(), + supports_function_calling: z.boolean().nullish(), + supports_vision: z.boolean().nullish(), + input_cost_per_token: z.number().nonnegative().nullish(), + output_cost_per_token: z.number().nonnegative().nullish(), + cache_read_input_token_cost: z.number().nonnegative().nullish(), + cache_creation_input_token_cost: z.number().nonnegative().nullish(), +}).passthrough(); + +export const TensorXModel = z.object({ + model_name: z.string().min(1), + model_info: TensorXModelInfo, +}); + +export const TensorXResponse = z.object({ + data: z.array(TensorXModel), +}); + +export type TensorXModel = z.infer; + +export const tensorx = { + id: "tensorx", + name: "TensorX", + modelsDir: "providers/tensorx/models", + // /v1/model/info carries no reasoning controls and no side-channel field, so + // a created reasoner would be published with the runner's fallback + // `reasoning_options = []` — an assertion of "no caller control" that nothing + // here backs — and no `interleaved`. New IDs are reported for hand-authoring + // instead; updates to existing TOMLs are unaffected. + skipCreates: true, + preserveBaseModels: false, + // /v1/model/info returns a per-key view: a key scoped to a model group sees + // only that group, and chat requests for the rest fail with 403 rather than + // 404. Absence from the response is therefore not evidence that a model was + // retired, so local entries are never deleted on the strength of it. + deleteMissing: false, + sourceID(model) { + // Non-chat entries (embeddings, transcription, speech) are out of scope and + // are dropped silently; only unauthorable chat models get reported. + return model.model_info.mode === "chat" ? model.model_name : undefined; + }, + skippedNotice(ids) { + if (ids.length === 0) return []; + return [ + `${ids.length} TensorX chat models were not created: /v1/model/info publishes no name, description, or release date, and no reasoning controls or side-channel field, so a complete model cannot be authored safely.`, + `Add the lab entry under \`models/\` and hand-author \`reasoning_options\` / \`interleaved\` against the live API. Skipped remote IDs: ${ids.map((id) => `\`${id}\``).join(", ")}`, + ]; + }, + missingNotice(paths) { + if (paths.length === 0) return []; + return [ + `${paths.length} local TensorX models were absent from /v1/model/info and were retained for manual lifecycle review.`, + `That endpoint is a per-key view, so absence can mean the sync key lacks access rather than the model being retired: ${paths.map((path) => `\`${path}\``).join(", ")}`, + ]; + }, + async fetchModels() { + const apiKey = process.env.TENSORX_API_KEY; + if (!apiKey) { + throw new Error("TensorX sync requires TENSORX_API_KEY environment variable"); + } + const response = await fetch(API_ENDPOINT, { + headers: { Authorization: `Bearer ${apiKey}` }, + }); + if (!response.ok) { + throw new Error(`TensorX model info request failed: ${response.status} ${response.statusText}`); + } + return response.json(); + }, + parseModels(raw) { + const models = TensorXResponse.parse(raw).data; + const seen = new Set(); + return models.filter((model) => { + if (seen.has(model.model_name)) return false; + seen.add(model.model_name); + return true; + }); + }, + translateModel(model, context) { + if (model.model_info.mode !== "chat") return undefined; + + const existing = context.existing(model.model_name); + // A base_model already authored locally wins over re-derivation. With + // preserveBaseModels false the runner will not put it back, so a resolution + // miss here (dropped alias, renamed lab entry, ID drift) would otherwise + // flatten the inherited lab fields into the provider TOML. + const baseModel = existing?.base_model ?? resolveBaseModel(model.model_name); + // Nothing in this catalog can stand in for lab metadata — no display name, + // no description, no release date — so an unknown model is reported for + // hand-authoring instead of being invented. + if (baseModel === undefined && existing === undefined) return undefined; + + const built = buildTensorXModel(model, baseModel, existing); + return built === undefined ? undefined : { id: model.model_name, model: built }; + }, +} satisfies SyncProvider; + +function buildTensorXModel( + model: TensorXModel, + baseModel: string | undefined, + existing: ExistingModel | undefined, +): SyncedModel | undefined { + const info = model.model_info; + + const limit = { + context: info.max_input_tokens ?? info.max_tokens ?? existing?.limit?.context, + output: info.max_output_tokens ?? existing?.limit?.output, + }; + + const input = perMillion(info.input_cost_per_token) ?? existing?.cost?.input; + const output = perMillion(info.output_cost_per_token) ?? existing?.cost?.output; + // Pricing is published only as a complete pair. A base_model file validates + // against a deepPartial schema, so a half-resolved cost would be written out + // as real pricing rather than rejected; a full model would abort the sync. + const cost = input === undefined || output === undefined + ? existing?.cost + : { + ...existing?.cost, + input, + output, + cache_read: perMillion(info.cache_read_input_token_cost) ?? existing?.cost?.cache_read, + // cache_creation_input_token_cost is null for every model in the catalog, + // so a null is "not published" rather than "not charged". Keep the + // authored price; real values publish as soon as TensorX fills it in. + cache_write: perMillion(info.cache_creation_input_token_cost) ?? existing?.cost?.cache_write, + }; + + // Never bring a brand-new model into the catalog without real pricing. + if (existing === undefined && cost === undefined) return undefined; + + const toolFlags = [info.supports_tool_choice, info.supports_function_calling] + .filter((flag) => flag !== null && flag !== undefined); + + // supports_vision is the only modality signal the catalog carries, so + // `attachment` and `modalities.input` move together. Only `image` is edited — + // the flag says nothing about video or pdf — and `attachment` is then read + // back off the resulting list rather than off the flag, so a model that keeps + // video after losing image stays `attachment = true`. + const vision = info.supports_vision; + const inheritedInput = existing?.modalities?.input; + const modalities = vision === null || vision === undefined || inheritedInput === undefined + ? existing?.modalities + : { + ...existing?.modalities, + input: vision + ? (inheritedInput.includes("image") ? inheritedInput : [...inheritedInput, "image"]) + : inheritedInput.filter((modality) => modality !== "image"), + }; + const resolvedInput = modalities?.input; + const attachment = vision === null || vision === undefined || resolvedInput === undefined + ? existing?.attachment + : resolvedInput.some((modality) => modality !== "text"); + + // `existing` is the base-model-resolved view, so factorBaseModel drops every + // field that still matches the lab entry and keeps only the real deltas. + const values: Record = { + ...existing, + attachment, + reasoning: info.supports_reasoning ?? existing?.reasoning, + tool_call: toolFlags.length > 0 ? toolFlags.some(Boolean) : existing?.tool_call, + cost, + limit, + modalities, + }; + delete values.base_model; + delete values.base_model_omit; + + return baseModel === undefined + ? values as SyncedFullModel + : factorBaseModel(baseModel, values, limit, existing?.base_model_omit); +} + +// Dated snapshots TensorX serves that have no lab entry of their own. +const BASE_MODEL_ALIASES: Record = { + "deepseek/deepseek-r1-0528": "deepseek/deepseek-r1", + "deepseek/deepseek-r1-0625": "deepseek/deepseek-r1", +}; + +function resolveBaseModel(modelID: string): string | undefined { + // resolveCanonicalBaseModel owns the org-prefix map and matches metadata + // filenames case-insensitively, so `minimax/minimax-m3` still resolves to + // `models/minimax/MiniMax-M3.toml`. + return resolveCanonicalBaseModel(BASE_MODEL_ALIASES[modelID] ?? modelID); +} + +function perMillion(costPerToken: number | null | undefined): number | undefined { + if (costPerToken === null || costPerToken === undefined) return undefined; + return Math.round(costPerToken * 1_000_000 * 1e10) / 1e10; +} diff --git a/providers/tensorx/models/deepseek/deepseek-chat-v3.1.toml b/providers/tensorx/models/deepseek/deepseek-chat-v3.1.toml index 93143b88c6..f75eace0bf 100644 --- a/providers/tensorx/models/deepseek/deepseek-chat-v3.1.toml +++ b/providers/tensorx/models/deepseek/deepseek-chat-v3.1.toml @@ -1,3 +1,10 @@ +# Not reachable with the sync key (403, models=['public']), so nothing here +# could be measured against this host. providers/openrouter/models/deepseek/ +# deepseek-chat-v3.1.toml authors a toggle, so that control shape is copied +# rather than keeping a full effort enum. +# Toggle: chat_template_kwargs.thinking = true|false — the path every reachable +# DeepSeek model on this host uses, but unverified for this ID. +# [interleaved] is omitted rather than assumed, for the same reason. name = "DeepSeek Chat V3.1" description = "DeepSeek chat model for instruction following, coding, and analysis" family = "deepseek" @@ -10,10 +17,8 @@ tool_call = true knowledge = "2024-11" open_weights = true - [[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; "none" disables reasoning -values = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] +type = "toggle" [cost] input = 0.2 diff --git a/providers/tensorx/models/deepseek/deepseek-r1-0528.toml b/providers/tensorx/models/deepseek/deepseek-r1-0528.toml index 5584722dd5..2d2925531f 100644 --- a/providers/tensorx/models/deepseek/deepseek-r1-0528.toml +++ b/providers/tensorx/models/deepseek/deepseek-r1-0528.toml @@ -1,11 +1,14 @@ +# reasoning_effort is accepted but does not grade depth on this host, and none +# is rejected (400), so reasoning is always on. Matches the OpenRouter entry for +# this model. base_model = "deepseek/deepseek-r1" name = "DeepSeek R1-0528" release_date = "2025-05-28" last_updated = "2025-05-28" +reasoning_options = [] -[[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; reasoning is mandatory, "none" is rejected -values = ["minimal", "low", "medium", "high", "xhigh", "max"] +[interleaved] +field = "reasoning_content" [cost] input = 0.66 diff --git a/providers/tensorx/models/deepseek/deepseek-v3.2.toml b/providers/tensorx/models/deepseek/deepseek-v3.2.toml index dc3a096e9e..874da8eb6c 100644 --- a/providers/tensorx/models/deepseek/deepseek-v3.2.toml +++ b/providers/tensorx/models/deepseek/deepseek-v3.2.toml @@ -1,3 +1,8 @@ +# Effort: reasoning_effort = none|high. This host does not reason by default — +# reasoning_content is empty unless an effort value is sent — and none keeps it +# off. chat_template_kwargs.enable_thinking and thinking are no-ops. Measured +# by reasoning_content length; invalid values return 400 and no graded level +# was separable, so one on level is published. name = "DeepSeek V3.2" description = "DeepSeek chat model for instruction following, coding, and analysis" family = "deepseek" @@ -10,10 +15,12 @@ tool_call = true knowledge = "2025-05" open_weights = true - [[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; "none" disables reasoning -values = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] +type = "effort" +values = ["none", "high"] + +[interleaved] +field = "reasoning_content" [cost] input = 0.3 diff --git a/providers/tensorx/models/deepseek/deepseek-v4-flash-0731.toml b/providers/tensorx/models/deepseek/deepseek-v4-flash-0731.toml index 5c7bc69ad3..c109b56717 100644 --- a/providers/tensorx/models/deepseek/deepseek-v4-flash-0731.toml +++ b/providers/tensorx/models/deepseek/deepseek-v4-flash-0731.toml @@ -1,12 +1,21 @@ +# Toggle: chat_template_kwargs.thinking = true|false (default off) +# reasoning_effort is accepted but not honored. Measured by reasoning_content +# length — not usage.reasoning_tokens, which this host under-reports — low, +# high, max and an invalid value all fall in the same range, so this host is +# toggle-only despite the lab exposing effort high|max. base_model = "deepseek/deepseek-v4-flash-0731" [[reasoning_options]] -type = "toggle" # API: {"chat_template_kwargs": {"thinking": true}} (default off) +type = "toggle" + +[interleaved] +field = "reasoning_content" [cost] input = 0.25 -output = 0.30 -cache_read = 0.06 +output = 0.3 +cache_read = 0.0625 [limit] -context = 1048576 \ No newline at end of file +context = 1_048_576 +output = 64_000 diff --git a/providers/tensorx/models/deepseek/deepseek-v4-flash.toml b/providers/tensorx/models/deepseek/deepseek-v4-flash.toml index fdea8b9a03..08fa020600 100644 --- a/providers/tensorx/models/deepseek/deepseek-v4-flash.toml +++ b/providers/tensorx/models/deepseek/deepseek-v4-flash.toml @@ -1,7 +1,15 @@ +# Toggle: chat_template_kwargs.thinking = true|false (default off) +# reasoning_effort is accepted but not honored. Measured by reasoning_content +# length — not usage.reasoning_tokens, which this host under-reports — low, +# high, max and an invalid value all fall in the same range, so this host is +# toggle-only despite the lab exposing effort high|max. base_model = "deepseek/deepseek-v4-flash" [[reasoning_options]] -type = "toggle" # API: {"chat_template_kwargs": {"thinking": true}} (default off) +type = "toggle" + +[interleaved] +field = "reasoning_content" [cost] input = 0.15 @@ -10,4 +18,5 @@ cache_read = 0.0375 cache_write = 0.1875 [limit] -context = 1048576 +context = 1_048_576 +output = 64_000 diff --git a/providers/tensorx/models/deepseek/deepseek-v4-pro.toml b/providers/tensorx/models/deepseek/deepseek-v4-pro.toml index ebe06937ea..fec75b2d13 100644 --- a/providers/tensorx/models/deepseek/deepseek-v4-pro.toml +++ b/providers/tensorx/models/deepseek/deepseek-v4-pro.toml @@ -1,7 +1,15 @@ +# Toggle: chat_template_kwargs.thinking = true|false (default off) +# reasoning_effort is accepted but not honored. Measured by reasoning_content +# length — not usage.reasoning_tokens, which this host under-reports — low, +# high, max and an invalid value all fall in the same range, so this host is +# toggle-only despite the lab exposing effort high|max. base_model = "deepseek/deepseek-v4-pro" [[reasoning_options]] -type = "toggle" # API: {"chat_template_kwargs": {"thinking": true}} (default off) +type = "toggle" + +[interleaved] +field = "reasoning_content" [cost] input = 1.75 @@ -10,4 +18,5 @@ cache_read = 0.4375 cache_write = 2.185 [limit] -context = 1048576 +context = 1_048_576 +output = 64_000 diff --git a/providers/tensorx/models/minimax/minimax-m2.5.toml b/providers/tensorx/models/minimax/minimax-m2.5.toml index b9c42b9646..3fa9dbfe7f 100644 --- a/providers/tensorx/models/minimax/minimax-m2.5.toml +++ b/providers/tensorx/models/minimax/minimax-m2.5.toml @@ -1,8 +1,12 @@ +# No caller control. reasoning_effort = none is rejected (400), low/medium/ +# high/max overlap entirely over 4 runs each, and chat_template_kwargs +# enable_thinking and thinking are both no-ops. Measured by reasoning_content +# length. Reasoning is always on, matching the lab and OpenRouter entries. base_model = "minimax/MiniMax-M2.5" +reasoning_options = [] -[[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; reasoning is mandatory, "none" is rejected -values = ["minimal", "low", "medium", "high", "xhigh", "max"] +[interleaved] +field = "reasoning_content" [cost] input = 0.3 diff --git a/providers/tensorx/models/minimax/minimax-m3.toml b/providers/tensorx/models/minimax/minimax-m3.toml index d9b2cb7dcb..48eff16652 100644 --- a/providers/tensorx/models/minimax/minimax-m3.toml +++ b/providers/tensorx/models/minimax/minimax-m3.toml @@ -1,7 +1,11 @@ +# Toggle: chat_template_kwargs.thinking_mode = enabled|disabled (default "adaptive") base_model = "minimax/MiniMax-M3" [[reasoning_options]] -type = "toggle" # API: {"chat_template_kwargs": {"thinking_mode": "enabled" | "disabled"}} (default "adaptive") +type = "toggle" + +[interleaved] +field = "reasoning_content" [cost] input = 0.4 @@ -9,5 +13,5 @@ output = 2 cache_read = 0.1 [limit] -context = 1048576 -output = 131072 +context = 1_048_576 +output = 64_000 diff --git a/providers/tensorx/models/moonshotai/kimi-k2.5.toml b/providers/tensorx/models/moonshotai/kimi-k2.5.toml index 83821146bc..db3656d067 100644 --- a/providers/tensorx/models/moonshotai/kimi-k2.5.toml +++ b/providers/tensorx/models/moonshotai/kimi-k2.5.toml @@ -1,6 +1,13 @@ +# No caller control. Measured by reasoning_content length over 6 runs each, +# chat_template_kwargs.thinking = false and reasoning_effort = none both leave +# reasoning_content filled (0/6 empty). usage.reasoning_tokens is not the +# signal here because this host under-reports it. Reasoning is always on. base_model = "moonshotai/kimi-k2.5" reasoning_options = [] +[interleaved] +field = "reasoning_content" + [cost] input = 0.5 output = 2.8 diff --git a/providers/tensorx/models/moonshotai/kimi-k2.6.toml b/providers/tensorx/models/moonshotai/kimi-k2.6.toml index 4490b5bfe1..3d926729dc 100644 --- a/providers/tensorx/models/moonshotai/kimi-k2.6.toml +++ b/providers/tensorx/models/moonshotai/kimi-k2.6.toml @@ -1,10 +1,20 @@ +# Toggle: chat_template_kwargs.thinking = true|false (default on); verified by +# reasoning_content going empty when false. +# reasoning_effort is accepted but not honored: none, low, high, max and an +# invalid value all leave reasoning_content in the same range. base_model = "moonshotai/kimi-k2.6" [[reasoning_options]] -type = "toggle" # API: {"chat_template_kwargs": {"thinking": false}} (default on) +type = "toggle" + +[interleaved] +field = "reasoning_content" [cost] input = 1 output = 4 cache_read = 0.25 cache_write = 1.25 + +[limit] +output = 64_000 diff --git a/providers/tensorx/models/moonshotai/kimi-k2.7-code.toml b/providers/tensorx/models/moonshotai/kimi-k2.7-code.toml index 77034868c0..e9088bbfec 100644 --- a/providers/tensorx/models/moonshotai/kimi-k2.7-code.toml +++ b/providers/tensorx/models/moonshotai/kimi-k2.7-code.toml @@ -1,9 +1,19 @@ +# Toggle: chat_template_kwargs.thinking = true|false (default on); verified by +# reasoning_content going empty when false. +# reasoning_effort is accepted but not honored: none, low, high, max and an +# invalid value all leave reasoning_content in the same range. base_model = "moonshotai/kimi-k2.7-code" [[reasoning_options]] -type = "toggle" # API: {"chat_template_kwargs": {"thinking": false}} (default on) +type = "toggle" + +[interleaved] +field = "reasoning_content" [cost] input = 1.25 output = 4.5 cache_read = 0.3125 + +[limit] +output = 64_000 diff --git a/providers/tensorx/models/moonshotai/kimi-k3.toml b/providers/tensorx/models/moonshotai/kimi-k3.toml index c5f9a35187..4985c54953 100644 --- a/providers/tensorx/models/moonshotai/kimi-k3.toml +++ b/providers/tensorx/models/moonshotai/kimi-k3.toml @@ -1,10 +1,22 @@ # AI SDK auto-discovers native IDs via /v1/models — field values map those IDs +# Effort: reasoning_effort = none|low|high|max, measured by reasoning_content +# length. none empties it in 9/9 runs and depth grows low < high < max; +# invalid values return 400. chat_template_kwargs.thinking = false is +# unreliable here (reasoning still streamed in 3/9 runs), so none is the +# published off switch and no toggle is declared. base_model = "moonshotai/kimi-k3" [[reasoning_options]] -type = "toggle" # API: {"chat_template_kwargs": {"thinking": false}} (default on) +type = "effort" +values = ["none", "low", "high", "max"] + +[interleaved] +field = "reasoning_content" [cost] -input = 3.00 -output = 15.00 -cache_read = 0.75 \ No newline at end of file +input = 3 +output = 15 +cache_read = 0.75 + +[limit] +output = 64_000 diff --git a/providers/tensorx/models/nvidia/nemotron-3-super-120b-a12b.toml b/providers/tensorx/models/nvidia/nemotron-3-super-120b-a12b.toml index 279eb86ea6..770b6699ed 100644 --- a/providers/tensorx/models/nvidia/nemotron-3-super-120b-a12b.toml +++ b/providers/tensorx/models/nvidia/nemotron-3-super-120b-a12b.toml @@ -1,8 +1,14 @@ +# Toggle: chat_template_kwargs.enable_thinking = true|false +# Documented for this model by Workers AI, another OpenAI-compatible host: +# https://developers.cloudflare.com/workers-ai/models/nemotron-3-120b-a12b/sync-input.json +# It is also the field every GLM model on this host uses. Not reachable with the +# sync key (403, models=['public']), so it could not be confirmed here; the +# control shape follows first-party providers/nvidia/models/nvidia/ +# nemotron-3-super-120b-a12b.toml. [interleaved] is omitted rather than assumed. base_model = "nvidia/nemotron-3-super-120b-a12b" [[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; "none" disables reasoning -values = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] +type = "toggle" [cost] input = 0.3 diff --git a/providers/tensorx/models/openai/gpt-oss-120b.toml b/providers/tensorx/models/openai/gpt-oss-120b.toml index b34a24534e..dfcf2045ea 100644 --- a/providers/tensorx/models/openai/gpt-oss-120b.toml +++ b/providers/tensorx/models/openai/gpt-oss-120b.toml @@ -1,9 +1,14 @@ +# Not reachable with the sync key (403, models=['public']), so nothing below +# could be measured against this host. Controls are carried from the lab/peer +# baseline; [interleaved] is deliberately omitted rather than assumed, even +# though all 18 reachable models here stream reasoning_content. +# OpenRouter, the closest same-surface peer, exposes low|medium|high. base_model = "openai/gpt-oss-120b" knowledge = "2024-10" [[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; reasoning is mandatory, "none" is rejected -values = ["minimal", "low", "medium", "high", "xhigh", "max"] +type = "effort" +values = ["low", "medium", "high"] [cost] input = 0.04 diff --git a/providers/tensorx/models/qwen/qwen3.5-122b-a10b.toml b/providers/tensorx/models/qwen/qwen3.5-122b-a10b.toml index 063495e1fb..60fb5c6822 100644 --- a/providers/tensorx/models/qwen/qwen3.5-122b-a10b.toml +++ b/providers/tensorx/models/qwen/qwen3.5-122b-a10b.toml @@ -1,8 +1,16 @@ +# Effort: reasoning_effort = none|high. Off is the effort value none, not a +# boolean field: chat_template_kwargs.enable_thinking and thinking are both +# accepted but are no-ops here. Measured by reasoning_content length (this +# host under-reports usage.reasoning_tokens); none empties it, invalid values +# return 400, and no graded level was separable, so one on level is published. base_model = "alibaba/qwen3.5-122b-a10b" [[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; "none" disables reasoning -values = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] +type = "effort" +values = ["none", "high"] + +[interleaved] +field = "reasoning_content" [cost] input = 0.5 diff --git a/providers/tensorx/models/qwen/qwen3.5-9b.toml b/providers/tensorx/models/qwen/qwen3.5-9b.toml index 93d4f8b02f..0f6d4f879f 100644 --- a/providers/tensorx/models/qwen/qwen3.5-9b.toml +++ b/providers/tensorx/models/qwen/qwen3.5-9b.toml @@ -1,12 +1,22 @@ +# Effort: reasoning_effort = none|high. Off is the effort value none, not a +# boolean field: chat_template_kwargs.enable_thinking and thinking are no-ops +# here. Measured by reasoning_content length; none empties it, invalid values +# return 400, and over 4 runs each low/medium/high/max overlap entirely +# (means within 60 chars), so one on level is published. base_model = "alibaba/qwen3.5-9b" - [[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; "none" disables reasoning -values = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] +type = "effort" +values = ["none", "high"] + +[interleaved] +field = "reasoning_content" [cost] input = 0.15 output = 0.2 cache_read = 0.0375 cache_write = 0.1875 + +[limit] +output = 262_144 diff --git a/providers/tensorx/models/z-ai/glm-4.7.toml b/providers/tensorx/models/z-ai/glm-4.7.toml index 931c963e6a..e0fcce5b2a 100644 --- a/providers/tensorx/models/z-ai/glm-4.7.toml +++ b/providers/tensorx/models/z-ai/glm-4.7.toml @@ -1,8 +1,13 @@ +# Not reachable with the sync key (403, models=['public']), so nothing below +# could be measured against this host. Controls are carried from the lab/peer +# baseline; [interleaved] is deliberately omitted rather than assumed, even +# though all 18 reachable models here stream reasoning_content. +# First-party z.ai GLM-4.7 is toggle-only. +# Toggle: chat_template_kwargs.enable_thinking = true|false base_model = "zhipuai/glm-4.7" [[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; "none" disables reasoning -values = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] +type = "toggle" [cost] input = 0.6 diff --git a/providers/tensorx/models/z-ai/glm-5-turbo.toml b/providers/tensorx/models/z-ai/glm-5-turbo.toml index 4e557e1c0c..9f444b466d 100644 --- a/providers/tensorx/models/z-ai/glm-5-turbo.toml +++ b/providers/tensorx/models/z-ai/glm-5-turbo.toml @@ -1,11 +1,16 @@ +# Toggle: chat_template_kwargs.enable_thinking = true|false (default on). +# Verified: false empties reasoning_content, true and the default fill it. +# This host streams reasoning into reasoning_content but does not count it in +# usage.reasoning_tokens, which reads 0 even while reasoning runs — measure +# the field, not the token count. reasoning_effort = none also suppresses it, +# but no graded level was separable, so only on/off is published. base_model = "zhipuai/glm-5-turbo" [[reasoning_options]] -type = "toggle" # API: {"chat_template_kwargs": {"enable_thinking": false}} (default on) +type = "toggle" -[[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; "none" disables reasoning -values = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] +[interleaved] +field = "reasoning_content" [cost] input = 1.2 diff --git a/providers/tensorx/models/z-ai/glm-5.1.toml b/providers/tensorx/models/z-ai/glm-5.1.toml index 869157ad21..5378fc7c25 100644 --- a/providers/tensorx/models/z-ai/glm-5.1.toml +++ b/providers/tensorx/models/z-ai/glm-5.1.toml @@ -1,11 +1,13 @@ +# Toggle: chat_template_kwargs.enable_thinking = true|false (default on) +# reasoning_effort is accepted but its graded levels were not separable here; +# first-party z.ai exposes only the toggle, so that is the declared control. base_model = "zhipuai/glm-5.1" [[reasoning_options]] -type = "toggle" # API: {"chat_template_kwargs": {"enable_thinking": false}} (default on) +type = "toggle" -[[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; "none" disables reasoning -values = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] +[interleaved] +field = "reasoning_content" [cost] input = 1.4 @@ -14,5 +16,5 @@ cache_read = 0.35 cache_write = 1.75 [limit] -context = 202752 -output = 202752 +context = 202_752 +output = 64_000 diff --git a/providers/tensorx/models/z-ai/glm-5.2.toml b/providers/tensorx/models/z-ai/glm-5.2.toml index 04350a241e..5bc8c2f958 100644 --- a/providers/tensorx/models/z-ai/glm-5.2.toml +++ b/providers/tensorx/models/z-ai/glm-5.2.toml @@ -1,11 +1,17 @@ +# Toggle: chat_template_kwargs.enable_thinking = true|false (default on) +# Effort: reasoning_effort = high|max — z.ai maps none|minimal to off, +# low|medium to high and xhigh to max, so high|max are the effective levels. base_model = "zhipuai/glm-5.2" [[reasoning_options]] -type = "toggle" # API: {"chat_template_kwargs": {"enable_thinking": false}} (default on) +type = "toggle" [[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; "none" disables reasoning -values = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] +type = "effort" +values = ["high", "max"] + +[interleaved] +field = "reasoning_content" [cost] input = 1.5 @@ -13,4 +19,5 @@ output = 4.5 cache_read = 0.375 [limit] -context = 1048576 +context = 1_048_576 +output = 64_000 diff --git a/providers/tensorx/models/z-ai/glm-5.toml b/providers/tensorx/models/z-ai/glm-5.toml index 6d547a7361..0696922a62 100644 --- a/providers/tensorx/models/z-ai/glm-5.toml +++ b/providers/tensorx/models/z-ai/glm-5.toml @@ -1,8 +1,16 @@ +# Effort: reasoning_effort = none|high. Off is the effort value none, not a +# boolean field: chat_template_kwargs.enable_thinking is accepted but is a no-op +# on this host, so no toggle is declared. Verified by reasoning_content going +# empty for none, not by usage.reasoning_tokens, which this host under-reports. +# No graded level was separable from another, so one on level is published. base_model = "zhipuai/glm-5" [[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; "none" disables reasoning -values = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] +type = "effort" +values = ["none", "high"] + +[interleaved] +field = "reasoning_content" [cost] input = 1 diff --git a/providers/tensorx/models/z-ai/glm-5v-turbo.toml b/providers/tensorx/models/z-ai/glm-5v-turbo.toml index c16e5d0679..b884c3f19d 100644 --- a/providers/tensorx/models/z-ai/glm-5v-turbo.toml +++ b/providers/tensorx/models/z-ai/glm-5v-turbo.toml @@ -1,11 +1,16 @@ +# Toggle: chat_template_kwargs.enable_thinking = true|false (default on). +# Verified: false empties reasoning_content, true and the default fill it. +# This host streams reasoning into reasoning_content but does not count it in +# usage.reasoning_tokens, which reads 0 even while reasoning runs — measure +# the field, not the token count. reasoning_effort = none also suppresses it, +# but no graded level was separable, so only on/off is published. base_model = "zhipuai/glm-5v-turbo" [[reasoning_options]] -type = "toggle" # API: {"chat_template_kwargs": {"enable_thinking": false}} (default on) +type = "toggle" -[[reasoning_options]] -type = "effort" # API: {"reasoning_effort": }; "none" disables reasoning -values = ["none", "minimal", "low", "medium", "high", "xhigh", "max"] +[interleaved] +field = "reasoning_content" [cost] input = 1.2