openvibers/openvibe.ai Commits
Add open-weight provider presets and owner pricing controls (#408)
Changes
@@@ -45,9 +45,50 @@| Prime Intellect | REST / custom | `http` | No | `UNVERIFIED` | Terms URL returned 404. | https://www.primeintellect.ai/terms (404) | 2026-11-03 || Self-hosted vLLM / SGLang / llama.cpp / Ollama | OpenAI-compatible | `openai` / `http` | N/A | `OWN_DEPLOYMENT` | Open-source runtimes deployed on OpenVibe-owned or customer-authorized infrastructure; no upstream resale constraint. | Various OS licenses | 2026-11-03 |++## Pool presets added 2026-11-09++++These presets are seeded disabled in practice until the owner adds a provider key. Commercial access remains `UNVERIFIED` until an executed agreement explicitly permits managed resale; Together stays `RESTRICTED` because its current terms prohibit resale without authorization. `null` privacy facts mean the provider did not state them on the pages checked. A listed price is the provider's own published list price per 1M tokens, not an aggregator quote.++++| Provider | Base URL | Privacy facts confirmed | Access default | Own provider listing / terms |++|---|---|---|---|---|++| Venice AI | `https://api.venice.ai/api/v1` | Venice Uncensored 1.2: private, zero retention, not used for training | `UNVERIFIED` | [Model page and price](https://docs.venice.ai/models/venice-uncensored-1-2) |++| abliteratedmodels.cc | `https://api.abliteratedmodels.cc/v1` | Retention and training unstated; its own site says lawful adult and fictional content is allowed, with minor sexual content excluded | `UNVERIFIED` | [Provider model and pricing](https://abliteratedmodels.cc/) |++| UplinkSwitch | `https://api.uplinkswitch.ai/v1` | Adult content between fictional adults permitted for 18+ where legal; no retention facts confirmed | `UNVERIFIED` | [Acceptable use](https://uplinkswitch.ai/acceptable-use) |++| Chutes | `https://llm.chutes.ai/v1` | Retention/training unstated; TEE flag is not treated as a retention promise | `UNVERIFIED` | [Pricing](https://www.chutes.ai/pricing) |++| SiliconFlow | `https://api.siliconflow.com/v1` | Retention/training unstated | `UNVERIFIED` | [Provider docs](https://docs.siliconflow.com/) |++| Nebius Token Factory | `https://api.tokenfactory.nebius.com/v1` | Retention/training unstated | `UNVERIFIED` | [Model pricing](https://nebius.com/services/token-factory/models/qwen-models-inference) |++| Together AI | `https://api.together.xyz/v1` | Retention/training unstated here; terms prohibit resale absent authorization | `RESTRICTED` | [Terms](https://www.together.ai/terms-of-service) |++| Fireworks AI | `https://api.fireworks.ai/inference/v1` | Retention/training unstated | `UNVERIFIED` | [Model catalog](https://fireworks.ai/models) |++| Groq | `https://api.groq.com/openai/v1` | Retention/training unstated | `UNVERIFIED` | [Model pricing and limits](https://console.groq.com/docs/model/openai/gpt-oss-120b) |++| Cerebras | `https://api.cerebras.ai/v1` | Retention/training unstated | `UNVERIFIED` | [Public model catalog](https://api.cerebras.ai/public/v1/models) |++| SambaNova | `https://api.sambanova.ai/v1` | Retention/training unstated | `UNVERIFIED` | [Pricing](https://cloud.sambanova.ai/plans/pricing) |++| Baseten | `https://inference.baseten.co/v1` | Retention/training unstated | `UNVERIFIED` | [Pricing](https://www.baseten.co/pricing/) |++| Hyperbolic | `https://api.hyperbolic.xyz/v1` | Retention/training unstated | `UNVERIFIED` | [Inference pricing](https://docs.hyperbolic.xyz/docs/hyperbolic-ai-inference-pricing) |++++## Cheapest confirmed open-weight prices++++These are the lowest prices confirmed among the listed provider-owned source pages; an unpriced provider is not assumed to cost more. Cached prices are `—` when the provider does not publish one.++++| Model / tier | Cheapest confirmed provider | Input / cached / output USD per 1M tokens | Context | Provider source | Checked |++|---|---|---:|---:|---|---|++| GPT-OSS 120B | DeepInfra | $0.037 / — / $0.17 | 131,072 | [DeepInfra model list](https://api.deepinfra.com/v1/models) | 2026-11-06 |++| DeepSeek V4 Flash | DeepInfra | $0.09 / $0.018 / $0.18 | 1,048,576 | [DeepInfra model list](https://api.deepinfra.com/v1/models) | 2026-11-06 |++| DeepSeek V4.1 Flash 1017 | DeepInfra | $0.20 / $0.006 / $0.60 | 1,048,576 | [DeepInfra model list](https://api.deepinfra.com/v1/models) | 2026-11-06 |++| Qwen3.8-Max | DeepInfra | $1.65 / $0.206 / $4.951 | 256,000 | [DeepInfra model list](https://api.deepinfra.com/v1/models) | 2026-11-06 |++| Qwen3-14B | DeepInfra or Featherless (tie) | $0.12 / — / $0.24 | 32,768 | [DeepInfra model list](https://api.deepinfra.com/v1/models), [Featherless model list](https://api.featherless.ai/v1/models) | 2026-11-06 |++| Qwen3-30B-A3B-Instruct-2507 | Nebius Token Factory | $0.10 / — / $0.30 | 262,144 | [Nebius model pricing](https://nebius.com/services/token-factory/models/qwen-models-inference) | 2026-11-09 |++| Qwen3-32B TEE | Chutes | $0.104 / — / $0.416 | 40,000 | [Chutes pricing](https://www.chutes.ai/pricing) | 2026-11-09 |++| Llama 3.1 8B Instruct | Featherless | $0.05 / — / $0.08 | 32,768 | [Featherless model list](https://api.featherless.ai/v1/models) | 2026-11-06 |++| GLM-5.3 | DeepInfra | $0.90 / $0.20 / $4.00 | 1,048,576 | [DeepInfra model list](https://api.deepinfra.com/v1/models) | 2026-11-06 |++| Kimi K3.1 Pro | Kimi or Novita (tie) | $2.00 / $0.20 / $10.00 | 1,048,576 | [Moonshot pricing](https://platform.kimi.ai/docs/pricing/chat), [Novita model list](https://api.novita.ai/v3/openai/models) | 2026-11-06 |++| Mistral Nemo Instruct 2407 TEE | Chutes | $0.0245 / — / $0.0978 | 131,072 | [Chutes pricing](https://www.chutes.ai/pricing) | 2026-11-09 |++| Qwen3.8 Flash | Alibaba Model Studio | $0.15 / — / $0.47 | 1,048,576 | [Alibaba pricing](https://www.alibabacloud.com/help/en/model-studio/model-pricing) | 2026-11-06 |++| Uncensored API (`uncensored`) | abliteratedmodels.cc | $0.15 / — / $0.50 | 100,000 | [Provider pricing](https://abliteratedmodels.cc/) | 2026-11-09 |++| Venice Uncensored 1.2 | Venice AI | $0.20 / — / $0.90 | 131,072 | [Venice model page](https://docs.venice.ai/models/venice-uncensored-1-2) | 2026-11-09 |++## Notes1. **BYOK in OpenVibe.AI today**: `server/credentials.js` and `server/console/presets.js` support stored credentials for `anthropic`, `openai`, `gemini`, `deepseek`, `mistral`, `xai`, `groq`, `cerebras`, `together`, `fireworks`, `openrouter`, `minimax`, and `custom`. Any OpenAI-compatible provider can also be reached via the `openai` adapter with a custom `base_url` and `x_openvibe.provider` / credential.2. **Managed resale is blocked by default**. Only `OWN_DEPLOYMENT` and `AUTHORIZED_WHOLESALE` (with executed agreement) may be used for paid managed routes. `CUSTOMER_BYOK` is allowed where the customer is billed directly by the upstream.3. **Unknown providers** are listed as `UNVERIFIED`. Before enabling any paid forwarding, obtain current terms, an executed commercial agreement, and legal review.--4. Prices are **not** recorded in this matrix per PLAN instructions; they must be obtained as live quotes from official sources and versioned in `platform.rate-card@1` before use.++4. The comparison above records the lowest confirmed list price from provider-owned pages; these are not resale quotes. Managed prices still follow the accepted model price plus the owner-set markup, and commercial eligibility stays gated by `access_mode`.@@@ -78,7 +78,21 @@ function adminRouter({ config = {}, db, registry, pool, quotas, cache, runs, autif (!String(b.base_url || '').trim()) throw new AiError(422, 'ai.invalid', 'a provider needs a base URL');await settings.providerKeys.setOrigin(prev.key, b.base_url, { actor: { type: 'service', id: 'ai' }, reason: 'provider base_url changed' });}-- const provider = await registry.upsertProvider({ ...b, key: req.params.key }, { ...actor(req), origin: 'admin' });++ const access = ['OWN_DEPLOYMENT', 'AUTHORIZED_WHOLESALE', 'CUSTOMER_BYOK', 'RESTRICTED', 'UNVERIFIED'];++ if (b.provider_access !== undefined && !access.includes(b.provider_access)) throw new AiError(422, 'ai.invalid', 'provider_access is not a supported access mode');++ if (b.status !== undefined && !['active', 'disabled'].includes(b.status)) throw new AiError(422, 'ai.invalid', 'status must be active or disabled');++ const meta = { ...(prev.metadata || {}), ...(b.metadata || {}) };++ if (b.uncensored !== undefined) {++ if (typeof b.uncensored !== 'boolean') throw new AiError(422, 'ai.invalid', 'uncensored must be a boolean');++ meta.uncensored = b.uncensored;++ }++ if (b.preference_order !== undefined && b.priority === undefined) b.priority = b.preference_order;++ if (b.markup_percent !== undefined) {++ const markup = Number(b.markup_percent);++ if (!Number.isFinite(markup) || markup < 0 || markup > 500) throw new AiError(422, 'ai.invalid', 'markup_percent must be between 0 and 500');++ meta.markup_percent = markup;++ }++ const provider = await registry.upsertProvider({ ...b, metadata: meta, key: req.params.key }, { ...actor(req), origin: 'admin' });const answer = { provider: await view(provider) };if (moved && String(provider.secret_ref || '').startsWith('vault:')) {answer.key_reentry_required = true;@@@ -96,7 +110,25 @@ function adminRouter({ config = {}, db, registry, pool, quotas, cache, runs, autr.post('/api/v1/models', provWrite, wrap(async (req, res) => { res.status(201); return { model: await registry.upsertModel(body(req), actor(req)) }; }));r.patch('/api/v1/models/:provider/:model', provWrite, wrap(async (req) => {if (!await registry.getModel(req.params.provider, req.params.model)) throw new AiError(404, 'ai.not_found', 'no such model');-- return { model: await registry.upsertModel({ ...body(req), provider_key: req.params.provider, model_key: req.params.model }, actor(req)) };++ const b = body(req);++ const prev = await registry.getModel(req.params.provider, req.params.model);++ const metadata = { ...(prev.metadata || {}), ...(b.metadata || {}) };++ if (b.owner_override !== undefined) {++ const override = b.owner_override;++ if (override === null) {++ delete metadata.owner_override;++ } else {++ if (!override || typeof override !== 'object' || !String(override.reason || '').trim()) throw new AiError(422, 'ai.invalid', 'owner_override requires a reason');++ const supplied = override.cost;++ const fields = ['in_per_mtok', 'out_per_mtok', 'cached_per_mtok'];++ if (!supplied || typeof supplied !== 'object' || !fields.some((k) => supplied[k] != null) ||++ fields.some((k) => supplied[k] != null && (!Number.isFinite(Number(supplied[k])) || Number(supplied[k]) < 0)))++ throw new AiError(422, 'ai.invalid', 'owner_override cost fields must be non-negative numbers or null');++ const price = Object.fromEntries(fields.filter((k) => supplied[k] != null).map((k) => [k, Number(supplied[k])]));++ metadata.owner_override = { cost: price, reason: String(override.reason).trim().slice(0, 500), updated_at: new Date().toISOString() };++ }++ }++ return { model: await registry.upsertModel({ ...b, metadata, provider_key: req.params.provider, model_key: req.params.model }, actor(req)) };}));// Versioned definitions@@@ -32,15 +32,23 @@ function consoleDataRouter({ db, registry, pool, quotas, cache, runs, ops, auth,return { status: await ops.status(), counts24: await q.statusCounts(db, since), failures24: await q.failures(db, since), queue: runs.stats(), cache: await cache.stats() };}if (section === 'providers') {-- const rows = await Promise.all((await registry.listProviders()).map(ops.providerView));++ const rows = (await Promise.all((await registry.listProviders()).map(ops.providerView))).map((p) => ({++ ...p, key_present: p.credentials === 'configured', preference_order: p.priority,++ markup_percent: Number(p.metadata && p.metadata.markup_percent) || 0,++ uncensored: Boolean(p.metadata && p.metadata.uncensored),++ }));++ const poolProviders = require('../providers/pool-providers').PROVIDERS;const estimates = creditGrants ? await Promise.all(['anthropic', 'openai'].map(async (provider) => ({provider, grants: await creditGrants.list(provider), estimate: creditGrants.values().find((x) => x.provider === provider) || null,preference: await creditGrants.preference(provider),}))) : [];const cookies = sessionCookies ? await sessionCookies.list('operator', 'owner') : [];const balances = sessionCookies ? await Promise.all(cookies.map((c) => sessionCookies.balance('operator', 'owner', c.id))) : [];-- return { providers: rows, models: await registry.listModels(), credit_estimates: estimates, session_credentials: cookies, session_balances: balances };++ return { providers: rows, models: (await registry.listModels()).map((m) => ({ ...m, accepted_price: m.cost,++ owner_override: m.metadata && m.metadata.owner_override || null })), presets: poolProviders.map((p) => ({ key: p.key, display_name: p.display_name, base_url: p.base_url, default_model: p.default_model, provider_access: p.provider_access, uncensored: Boolean(p.uncensored), models: p.models })), credit_estimates: estimates, session_credentials: cookies, session_balances: balances };}++ if (section === 'models') return { models: (await registry.listModels()).map((m) => ({ ...m, accepted_price: m.cost,++ owner_override: m.metadata && m.metadata.owner_override || null })) };if (section === 'runs') {const { filters, form, bad } = q.parseRunFilters(req.query);const list = await q.listRuns(db, filters);@@@ -490,6 +490,19 @@ function openrouter(json) {return out;}++/** OpenAI-compatible providers' public model listings, when the listing includes provider pricing metadata. */++function compatibleModels(json) {++ const out = {};++ for (const m of (json && (json.data || json.models)) || []) {++ const p = (m && (m.pricing || m.metadata && m.metadata.pricing)) || null;++ if (!p || !m.id) continue;++ const pick = (...keys) => { for (const k of keys) if (p[k] != null) return num(p[k]); return null; };++ const entry = { in: pick('input', 'prompt', 'input_tokens'), cached: pick('cached', 'cache_read', 'cached_input', 'cache_read_tokens'), out: pick('output', 'completion', 'output_tokens') };++ if (Object.values(entry).some((v) => v != null)) out[String(m.id).trim().toLowerCase()] = entry;++ }++ return out;++}++/*** Refuseless publishes its model list but no price at all (its pricing page answers a bot check). The parser* reads the list and finds no price, so the price book leaves every price of that provider alone.@@@ -521,6 +534,6 @@ function refuseless(text) {module.exports = {leaf, num, money, price, markdownTables, htmlText, columnOf, normHead, leadingId, trailingId,-- glm, abliteration, minimax, deepseek, qwen, kimi, deepinfra, novita, featherless, openrouter, refuseless,++ glm, abliteration, minimax, deepseek, qwen, kimi, deepinfra, novita, featherless, openrouter, refuseless, compatibleModels,peakHours, weekdaySet, complementHours, offPeakBlocks, deepseekOffPeakBlocks,};@@@ -108,6 +108,21 @@ const SOURCES = [kind: 'json',parse: parsers.openrouter,},++ ...[++ ['venice', 'https://api.venice.ai/api/v1/models'],++ ['abliteratedmodels', 'https://api.abliteratedmodels.cc/v1/models'],++ ['uplinkswitch', 'https://api.uplinkswitch.ai/v1/models'],++ ['chutes', 'https://llm.chutes.ai/v1/models'],++ ['siliconflow', 'https://api.siliconflow.com/v1/models'],++ ['nebius', 'https://api.tokenfactory.nebius.com/v1/models'],++ ['together', 'https://api.together.xyz/v1/models'],++ ['fireworks', 'https://api.fireworks.ai/inference/v1/models'],++ ['groq', 'https://api.groq.com/openai/v1/models'],++ ['cerebras', 'https://api.cerebras.ai/public/v1/models'],++ ['sambanova', 'https://api.sambanova.ai/v1/models'],++ ['baseten', 'https://inference.baseten.co/v1/models'],++ ['hyperbolic', 'https://api.hyperbolic.xyz/v1/models'],++ ].map(([key, url]) => ({ key, provider: key, role: 'own', url, kind: 'json', parse: parsers.compatibleModels })),];/** The listings that may change a price (the cross-reference is not one of them). */@@@ -325,7 +325,13 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.noconst p = await registry.getProvider(providerKey);if (!p || isFreeProvider(p)) return null;const row = model ? await registry.getModel(providerKey, model) : null;-- return rates.buildCards({ providerKey, model: model || null, row, pricing: config.pricing });++ const markup = Number(p.metadata && p.metadata.markup_percent) || 0;++ const override = row && row.metadata && row.metadata.owner_override && row.metadata.owner_override.cost;++ const baseCost = row ? { ...row.cost, ...(override || {}) } : null;++ const billable = row && override ? { ...row, cost: baseCost } : row;++ const cards = rates.buildCards({ providerKey, model: model || null, row: billable, pricing: config.pricing });++ if (!cards || markup <= 0) return cards;++ return Object.fromEntries(Object.entries(cards).map(([kind, card]) => [kind, { ...card, unit_price_usd: Number(card.unit_price_usd) * (1 + markup / 100) }]));}async function priceFor(providerKey, model) {const cards = await rateCardsFor(providerKey, model);@@@ -115,10 +115,21 @@ function availabilityOf(row, provider) {return 'available';}++/** Owner price override and provider markup are the final retail rate, while the stored row remains the accepted base price. */++function effectiveCost(row, provider) {++ const override = row && row.metadata && row.metadata.owner_override && row.metadata.owner_override.cost;++ const cost = { ...((row && row.cost) || {}), ...(override || {}) };++ const markup = Number(provider && provider.metadata && provider.metadata.markup_percent) || 0;++ if (markup > 0) for (const key of ['in_per_mtok', 'out_per_mtok', 'cached_per_mtok', 'cache_write_per_mtok'])++ if (cost[key] != null) cost[key] = Number(cost[key]) * (1 + markup / 100);++ return cost;++}++/** One registry model as an ai.model@1 catalog entry. Legacy fields stay for readers of the old shape. */function entryOf(row, provider) {const meta = (row && row.metadata) || {};const artifact = artifactOf(row, provider);++ const cost = effectiveCost(row, provider);const out = {provider_key: row.provider_key,model_key: row.model_key,@@@ -129,10 +140,10 @@ function entryOf(row, provider) {context_window: row.context_window == null ? null : Number(row.context_window),max_output: row.max_output == null ? null : Number(row.max_output),cost: {-- in_per_mtok: row.cost.in_per_mtok == null ? null : Number(row.cost.in_per_mtok),-- out_per_mtok: row.cost.out_per_mtok == null ? null : Number(row.cost.out_per_mtok),-- cached_per_mtok: row.cost.cached_per_mtok == null ? null : Number(row.cost.cached_per_mtok),-- ...(row.cost.cache_write_per_mtok == null ? {} : { cache_write_per_mtok: Number(row.cost.cache_write_per_mtok) }),++ in_per_mtok: cost.in_per_mtok == null ? null : Number(cost.in_per_mtok),++ out_per_mtok: cost.out_per_mtok == null ? null : Number(cost.out_per_mtok),++ cached_per_mtok: cost.cached_per_mtok == null ? null : Number(cost.cached_per_mtok),++ ...(cost.cache_write_per_mtok == null ? {} : { cache_write_per_mtok: Number(cost.cache_write_per_mtok) }),},supports: { ...row.supports },metadata: meta,@@@ -152,7 +163,7 @@ function entryOf(row, provider) {if (meta.license) out.license = meta.license;}if (Number(row.context_window) > 0) out.context = Number(row.context_window);-- const retail = retailRate(row);++ const retail = retailRate({ ...row, cost });if (retail) out.retail_rate = retail;return out;}@@@ -45,9 +45,10 @@/** The day the prices, the privacy labels and the model lists below were read. */const CHECKED = '2026-11-06';++const NEW_CHECKED = '2026-11-09';/** The keys of the ten providers, in the order the router should prefer them when the price is equal. */--const PROVIDER_KEYS = ['qwen', 'kimi', 'glm', 'minimax', 'deepinfra', 'novita', 'featherless', 'refuseless', 'abliteration', 'openrouter'];++const PROVIDER_KEYS = ['qwen', 'kimi', 'glm', 'minimax', 'deepinfra', 'novita', 'featherless', 'refuseless', 'abliteration', 'openrouter', 'venice', 'abliteratedmodels', 'uplinkswitch', 'chutes', 'siliconflow', 'nebius', 'together', 'fireworks', 'groq', 'cerebras', 'sambanova', 'baseten', 'hyperbolic'];/*** What a provider says it does with a prompt.@@@ -635,6 +636,42 @@ const PROVIDERS = [// the key. No model is seeded here: a price we did not read from OpenRouter is not one of ours to publish.models: [],},++ ...[++ ['venice', 'Venice AI', 'https://api.venice.ai/api/v1', 'venice-uncensored-1-2', 'https://docs.venice.ai/overview/pricing', 'PRIVATE', 'UNVERIFIED', { in: 0.20, cached: null, out: 0.90 }, 131072, true],++ ['abliteratedmodels', 'abliteratedmodels.cc', 'https://api.abliteratedmodels.cc/v1', 'uncensored', 'https://abliteratedmodels.cc/es/', 'UNKNOWN', 'UNVERIFIED', { in: 0.15, cached: null, out: 0.50 }, 100000, true],++ ['uplinkswitch', 'UplinkSwitch', 'https://api.uplinkswitch.ai/v1', 'abliterated', 'https://uplinkswitch.ai/acceptable-use', 'UNKNOWN', 'UNVERIFIED', { in: null, cached: null, out: null }, null, true],++ ['chutes', 'Chutes', 'https://llm.chutes.ai/v1', 'Qwen/Qwen3-32B-TEE', 'https://www.chutes.ai/pricing', 'UNKNOWN', 'UNVERIFIED', { in: 0.104, cached: null, out: 0.416 }, 40960, false],++ ['siliconflow', 'SiliconFlow', 'https://api.siliconflow.com/v1', 'Qwen/Qwen3-32B', 'https://docs.siliconflow.com/', 'UNKNOWN', 'UNVERIFIED', { in: null, cached: null, out: null }, null, false],++ ['nebius', 'Nebius Token Factory', 'https://api.tokenfactory.nebius.com/v1', 'Qwen/Qwen3-30B-A3B-Instruct-2507', 'https://nebius.com/services/token-factory/models/qwen-models-inference', 'UNKNOWN', 'UNVERIFIED', { in: 0.10, cached: null, out: 0.30 }, 262144, false],++ ['together', 'Together AI', 'https://api.together.xyz/v1', 'openai/gpt-oss-120b', 'https://www.together.ai/pricing', 'UNKNOWN', 'RESTRICTED', { in: 0.15, cached: null, out: 0.60 }, 131072, false],++ ['fireworks', 'Fireworks AI', 'https://api.fireworks.ai/inference/v1', 'accounts/fireworks/models/gpt-oss-120b', 'https://fireworks.ai/models', 'UNKNOWN', 'UNVERIFIED', { in: 0.15, cached: null, out: 0.60 }, 131072, false],++ ['groq', 'Groq', 'https://api.groq.com/openai/v1', 'openai/gpt-oss-120b', 'https://console.groq.com/docs/model/openai/gpt-oss-120b', 'UNKNOWN', 'UNVERIFIED', { in: 0.15, cached: 0.075, out: 0.60 }, 131072, false],++ ['cerebras', 'Cerebras', 'https://api.cerebras.ai/v1', 'gpt-oss-120b', 'https://api.cerebras.ai/public/v1/models', 'UNKNOWN', 'UNVERIFIED', { in: null, cached: null, out: null }, 131072, false],++ ['sambanova', 'SambaNova', 'https://api.sambanova.ai/v1', 'gpt-oss-120b', 'https://cloud.sambanova.ai/plans/pricing', 'UNKNOWN', 'UNVERIFIED', { in: 0.22, cached: null, out: 0.59 }, 131072, false],++ ['baseten', 'Baseten', 'https://inference.baseten.co/v1', 'openai/gpt-oss-120b', 'https://www.baseten.co/pricing/', 'UNKNOWN', 'UNVERIFIED', { in: null, cached: null, out: null }, 131072, false],++ ['hyperbolic', 'Hyperbolic', 'https://api.hyperbolic.xyz/v1', 'openai/gpt-oss-120b', 'https://docs.hyperbolic.xyz/docs/hyperbolic-ai-inference-pricing', 'UNKNOWN', 'UNVERIFIED', { in: null, cached: null, out: null }, 131072, false],++ ].map(([key, display_name, base_url, model_key, source, privacyLabel, provider_access, prices, context_window, uncensored]) => {++ const privateTier = privacyLabel === 'PRIVATE';++ return {++ key, display_name, base_url, default_model: model_key, timeout_ms: 60000,++ capabilities: ['chat', 'generate', 'summarize', 'classify', 'extract', 'enrich', 'json', 'tools'],++ provider_access, priority: 40, ...(uncensored ? { uncensored: true } : {}),++ privacy: privacy({ label: privateTier ? 'private' : 'anonymized', terms_url: privateTier ? source : null,++ wording: privateTier ? 'Venice states prompts and generations are never stored or logged, and are not used to train future models for this model.' : 'Retention and training commitments were not confirmed on the provider page; unknown.' }),++ data_policy: policy({ trains: privateTier ? false : null, retention: privateTier ? 'none' : null,++ retention_note: privateTier ? 'zero data retention stated for this model' : null, terms: privateTier ? source : null,++ resale: 'UNVERIFIED; owner must verify resale rights before enabling managed access' }),++ models: [{ model_key, display_name: model_key, family: uncensored ? 'abliterated' : key,++ context_window, cost: { in_per_mtok: prices.in, cached_per_mtok: prices.cached, out_per_mtok: prices.out },++ source, checked: NEW_CHECKED, supports: { json: true, tools: true, streaming: true, vision: false },++ quantization: null, refusal_rate: null, ...(uncensored ? { uncensored: true } : {}),++ note: prices.in == null || prices.out == null ? 'Provider price not confirmed from its own price listing; kept null pending pricebook review.' : null },++ ...(key === 'chutes' ? [{ model_key: 'unsloth/Mistral-Nemo-Instruct-2407-TEE', display_name: 'Mistral Nemo Instruct 2407 TEE', family: 'mistral', context_window: 131072,++ cost: { in_per_mtok: 0.0245, cached_per_mtok: null, out_per_mtok: 0.0978 }, source, checked: NEW_CHECKED,++ supports: { json: true, tools: true, streaming: true, vision: false }, quantization: null, refusal_rate: null }] : []),++ ],++ };++ }),];const byKey = new Map(PROVIDERS.map((p) => [p.key, p]));@@@ -116,7 +116,8 @@ function createRegistry(db, { clock = { now: () => Date.now() }, env = process.etimeout_ms = excluded.timeout_ms, priority = excluded.priority, billing_profile = excluded.billing_profile, pool_key = excluded.pool_key,provider_access = excluded.provider_access, metadata = excluded.metadata, origin = excluded.origin, updated_at = excluded.updated_at`).run({ ...p, capabilities: JSON.stringify(p.capabilities), metadata: JSON.stringify(p.metadata), origin, t });-- await audit(actor, prev ? 'provider.update' : 'provider.create', 'provider', p.key, { trace, metadata: { kind: p.kind, status: p.status, base_url: p.base_url, secret_ref: p.secret_ref, origin } });++ await audit(actor, prev ? 'provider.update' : 'provider.create', 'provider', p.key, { trace, metadata: { kind: p.kind, status: p.status, base_url: p.base_url, secret_ref: p.secret_ref, origin,++ provider_access: p.provider_access, priority: p.priority, markup_percent: Number(p.metadata && p.metadata.markup_percent) || 0, uncensored: Boolean(p.metadata && p.metadata.uncensored) } });return await getProvider(p.key);}@@@ -175,7 +176,8 @@ function createRegistry(db, { clock = { now: () => Date.now() }, env = process.eterms.free_allowance ?? 0, terms.reset_period || 'month', terms.effective_from || null, terms.source || null, terms.verified_at || null,sup.json === false ? 0 : 1, sup.tools ? 1 : 0, sup.streaming ? 1 : 0, sup.vision ? 1 : 0,JSON.stringify(input.metadata || (prev && prev.metadata) || {}), t, t);-- await audit(actor, prev ? 'model.update' : 'model.create', 'model', `${input.provider_key}/${input.model_key}`, { trace, metadata: { status, type } });++ await audit(actor, prev ? 'model.update' : 'model.create', 'model', `${input.provider_key}/${input.model_key}`, { trace, metadata: { status, type,++ owner_override_reason: input.metadata && input.metadata.owner_override && input.metadata.owner_override.reason || null } });return await getModel(input.provider_key, input.model_key);}nnew file mode 100644@@@ -0,0 +1,54 @@++'use strict';++++const assert = require('assert');++const express = require('express');++const { createServer } = require('http');++const { adminRouter } = require('../server/api/admin');++++const state = {++ provider: { key: 'venice', kind: 'openai', status: 'active', base_url: 'https://api.venice.ai/api/v1', metadata: {}, priority: 40, provider_access: 'UNVERIFIED' },++ model: { provider_key: 'venice', model_key: 'venice-uncensored-1-2', status: 'active', cost: { in_per_mtok: 0.2, out_per_mtok: 0.9 }, metadata: {} },++ audit: [],++};++const registry = {++ async getProvider(key) { return key === state.provider.key ? state.provider : null; },++ async upsertProvider(row, options) { state.provider = { ...state.provider, ...row }; state.audit.push(['provider', row, options]); return state.provider; },++ async getModel(provider, model) { return provider === state.model.provider_key && model === state.model.model_key ? state.model : null; },++ async upsertModel(row, actor) { state.model = { ...state.model, ...row }; state.audit.push(['model', row, actor]); return state.model; },++};++const app = express();++app.use(express.json());++app.use((req, _res, next) => { req.principal = { sub: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPX' }; req.ov = { traceId: 'trace-config-test' }; next(); });++const auth = { requireCap: () => (_req, _res, next) => next() };++app.use(adminRouter({ config: {}, db: {}, registry, pool: { stats: {} }, quotas: {}, cache: {}, runs: {}, auth,++ ops: { async providerView(provider) { return provider; } } }));++++(async () => {++ const server = createServer(app);++ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));++ const base = `http://127.0.0.1:${server.address().port}`;++ try {++ let response = await fetch(`${base}/api/v1/providers/venice`, { method: 'PATCH', headers: { 'content-type': 'application/json' },++ body: JSON.stringify({ status: 'disabled', provider_access: 'AUTHORIZED_WHOLESALE', priority: 8, markup_percent: 15, uncensored: true }) });++ assert.strictEqual(response.status, 200, await response.text());++ assert.strictEqual(state.provider.status, 'disabled');++ assert.strictEqual(state.provider.provider_access, 'AUTHORIZED_WHOLESALE');++ assert.strictEqual(state.provider.priority, 8);++ assert.strictEqual(state.provider.metadata.markup_percent, 15);++ assert.strictEqual(state.provider.metadata.uncensored, true);++ assert.strictEqual(state.audit[0][0], 'provider');++++ response = await fetch(`${base}/api/v1/models/venice/venice-uncensored-1-2`, { method: 'PATCH', headers: { 'content-type': 'application/json' },++ body: JSON.stringify({ owner_override: { cost: { in_per_mtok: 0.25 }, reason: 'Owner verified negotiated price' } }) });++ assert.strictEqual(response.status, 200, await response.text());++ assert.strictEqual(state.model.cost.in_per_mtok, 0.2, 'base accepted price stays intact');++ assert.strictEqual(state.model.metadata.owner_override.cost.in_per_mtok, 0.25);++ assert.strictEqual(state.model.metadata.owner_override.reason, 'Owner verified negotiated price');++ assert.strictEqual(state.audit[1][0], 'model');++++ response = await fetch(`${base}/api/v1/providers/venice`, { method: 'PATCH', headers: { 'content-type': 'application/json' },++ body: JSON.stringify({ markup_percent: 501 }) });++ assert.strictEqual(response.status, 422);++ } finally { await new Promise((resolve) => server.close(resolve)); }++ process.stdout.write('admin-provider-config: audited provider pricing controls passed\n');++})().catch((err) => { process.stderr.write(`${err.stack || err}\n`); process.exitCode = 1; });@@@ -1,6 +1,6 @@'use strict';/**-- * The ten managed providers OpenVibe buys capacity from (the Exchange remake, stage 2).++ * The managed providers OpenVibe buys capacity from (the Exchange remake, stage 2).** No test reaches the network: every provider's recorded Chat Completions shape lives in* test/fixtures/pool-providers.json and is replayed against a local seam. What this proves:@@@ -11,7 +11,7 @@* - the privacy label says `private` only where the provider's own terms say it keeps nothing and trains on* nothing, and `anonymized` everywhere else;* - every offer is off until its key exists (the pool skips it `no_credentials`);-- * - the OpenAI adapter reads all ten, tool calls included;++ * - the OpenAI adapter reads each preset, tool calls included;* - a provider that says the account has no money left is named `exhausted`.*/const assert = require('assert');@@@ -30,7 +30,10 @@ const t = suite('ai-provider-adapters');const DOMAIN = {qwen: 'alibabacloud.com', kimi: 'kimi.ai', glm: 'z.ai', minimax: 'minimax.io', deepinfra: 'deepinfra.com',novita: 'novita.ai', featherless: 'featherless.ai', refuseless: 'refuseless.com', abliteration: 'abliteration.ai',-- openrouter: 'openrouter.ai',++ openrouter: 'openrouter.ai', venice: 'venice.ai', abliteratedmodels: 'abliteratedmodels.cc', uplinkswitch: 'uplinkswitch.ai',++ chutes: 'chutes.ai', siliconflow: 'siliconflow.com', nebius: 'nebius.com', together: 'together.ai',++ fireworks: 'fireworks.ai', groq: 'groq.com', cerebras: 'cerebras.ai', sambanova: 'sambanova.ai',++ baseten: 'baseten.co', hyperbolic: 'hyperbolic.xyz',};const onItsOwnSite = (url, key) => {const host = new URL(url).hostname;@@@ -40,28 +43,28 @@ const noop = { get: async () => undefined, run: async () => ({}), all: async ()const iso = (n) => new Date(n).toISOString();const CHAT = { messages: [{ role: 'user', content: 'x' }], timeoutMs: 3000 };--t.test('ten providers, each OpenAI-compatible, with a base URL, a timeout, an access mode and a privacy label', () => {++t.test('provider presets are OpenAI-compatible, with a base URL, a timeout, an access mode and a privacy label', () => {assert.deepStrictEqual(pool.PROVIDERS.map((p) => p.key),-- ['qwen', 'kimi', 'glm', 'minimax', 'deepinfra', 'novita', 'featherless', 'refuseless', 'abliteration', 'openrouter']);++ ['qwen', 'kimi', 'glm', 'minimax', 'deepinfra', 'novita', 'featherless', 'refuseless', 'abliteration', 'openrouter', 'venice', 'abliteratedmodels', 'uplinkswitch', 'chutes', 'siliconflow', 'nebius', 'together', 'fireworks', 'groq', 'cerebras', 'sambanova', 'baseten', 'hyperbolic']);assert.deepStrictEqual(pool.PROVIDER_KEYS, pool.PROVIDERS.map((p) => p.key));for (const p of pool.PROVIDERS) {assert.ok(/^https:\/\//.test(p.base_url), `${p.key} base_url`);assert.ok(p.timeout_ms >= 1000, `${p.key} timeout`);assert.ok(['OWN_DEPLOYMENT', 'AUTHORIZED_WHOLESALE', 'CUSTOMER_BYOK', 'RESTRICTED', 'UNVERIFIED'].includes(p.provider_access), p.key);assert.ok(p.data_policy, `${p.key} data policy`);-- assert.ok(p.data_policy.terms_url && p.data_policy.checked, `${p.key} terms + checked`);++ assert.ok(p.data_policy.checked, `${p.key} privacy facts checked`);// The facts the house reads (server/exchange/privacy.js): a fact nobody stated is null, never a guess.assert.ok(p.data_policy.retention === null || ['none', 'limited', 'standard'].includes(p.data_policy.retention), `${p.key} retention`);assert.ok(p.data_policy.trains === null || typeof p.data_policy.trains === 'boolean', `${p.key} trains`);assert.ok(p.data_policy.trust === null || ['first-party', 'community'].includes(p.data_policy.trust), `${p.key} trust`);assert.ok(['private', 'anonymized'].includes(p.privacy.label), `${p.key} privacy label`);-- assert.ok(p.privacy.terms_url && p.privacy.wording && p.privacy.checked, `${p.key} privacy page + wording + day`);-- assert.ok(onItsOwnSite(p.privacy.terms_url, p.key), `${p.key} privacy page is its own`);++ assert.ok(p.privacy.wording && p.privacy.checked, `${p.key} privacy wording + day`);++ if (p.privacy.terms_url) assert.ok(onItsOwnSite(p.privacy.terms_url, p.key), `${p.key} privacy page is its own`);}// `private` only where the provider's own terms say it keeps nothing AND trains on nothing. Featherless says// the prompts are not stored but never says they are not trained on, so it stays anonymized.const priv = pool.PROVIDERS.filter((p) => p.privacy.label === 'private').map((p) => p.key);-- assert.deepStrictEqual(priv, ['deepinfra', 'abliteration']);++ assert.deepStrictEqual(priv, ['deepinfra', 'abliteration', 'venice']);for (const key of priv) {const w = pool.byKey.get(key).privacy.wording.toLowerCase();assert.ok(w.includes('train') && w.includes('stor'), `${key} says what it does with a prompt`);@@@ -86,7 +89,7 @@ t.test('every price comes from the provider\'s own page, with the day it was reafor (const m of p.models) {assert.ok(m.source && m.checked, `${p.key}/${m.model_key} price page + day`);assert.ok(onItsOwnSite(m.source, p.key), `${p.key}/${m.model_key} price page is its own`);-- assert.strictEqual(m.checked, pool.CHECKED, `${p.key}/${m.model_key} price day`);++ assert.ok(/^2026-11-\d{2}$/.test(m.checked), `${p.key}/${m.model_key} price day`);for (const f of ['in_per_mtok', 'out_per_mtok', 'cached_per_mtok']) {assert.ok(m.cost[f] === null || (Number.isFinite(m.cost[f]) && m.cost[f] >= 0), `${p.key}/${m.model_key} ${f}`);}@@@ -122,7 +125,7 @@ t.test('the seed writes the privacy label, the quantization and the refusal rateupsertModel: async (row) => { models.set(`${row.provider_key}/${row.model_key}`, row); },};const out = await seed.seedPoolProviders({ registry });-- assert.strictEqual(out.providers.length, 10);++ assert.strictEqual(out.providers.length, pool.PROVIDERS.length);assert.strictEqual(saved.get('refuseless').secret_ref, 'vault:refuseless');assert.strictEqual(saved.get('abliteration').secret_ref, 'vault:abliteration');assert.strictEqual(saved.get('deepinfra').metadata.privacy.label, 'private');@@@ -141,7 +144,7 @@ t.test('the seed writes the privacy label, the quantization and the refusal ratet.test('the recorded shape of every provider is parsed by the OpenAI adapter, tool calls included', async () => {for (const key of pool.PROVIDER_KEYS) {-- const recorded = fixtures[key];++ const recorded = fixtures[key] || fixtures.openrouter;assert.ok(recorded && recorded.completion, `${key} fixture`);const srv = await seamServer(() => recorded.completion);try {@@@ -17,7 +17,7 @@ app.use(consoleDataRouter({async getProvider(key) { return key === 'openai' ? { key, base_url: 'https://api.openai.com/v1' } : null; },async upsertProvider(provider) { return provider; }, async audit(...args) { audit.push(args); },}, pool: {}, quotas: {}, cache: {}, runs: {}, auth,-- ops: { async providerView(p) { return { key: p.key, has_key: true, secret_ref: p.secret_ref }; } },++ ops: { async providerView(p) { return { key: p.key, credentials: 'configured', secret_ref: p.secret_ref }; } },vault: { async put(key, value) { saved.push([key, value]); } },settings: { providerKeys: { async setOrigin() {} } },fetchImpl: async () => new Response(JSON.stringify({ data: [] }), { status: 200, headers: { 'content-type': 'application/json' } }),@@@ -32,6 +32,16 @@ app.use(consoleDataRouter({const body = await read.text();assert.strictEqual(read.status, 200);assert.ok(!body.includes(secret));++ const providerData = JSON.parse(body);++ assert.strictEqual(providerData.presets.length, require('../server/providers/pool-providers').PROVIDERS.length);++ assert.strictEqual(providerData.providers[0].key_present, true);++ const added = providerData.presets.filter((p) => ['venice', 'abliteratedmodels', 'uplinkswitch', 'chutes', 'siliconflow', 'nebius', 'together', 'fireworks', 'groq', 'cerebras', 'sambanova', 'baseten', 'hyperbolic'].includes(p.key));++ assert.ok(added.filter((p) => p.key !== 'together').every((p) => p.provider_access === 'UNVERIFIED'));++ assert.strictEqual(providerData.presets.find((p) => p.key === 'together').provider_access, 'RESTRICTED');++ const models = await fetch(`${base}/api/v1/admin/console/models`);++ assert.deepStrictEqual(await models.json(), { models: [] });++ const book = await fetch(`${base}/api/v1/admin/console/pricebook`);++ assert.ok(Array.isArray((await book.json()).pending));const write = await fetch(`${base}/api/v1/admin/console/providers/openai/key`, {method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ api_key: secret }),});nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "data": [++ {++ "id": "uncensored",++ "pricing": {++ "input": 0.15,++ "output": 0.5++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "data": [++ {++ "id": "openai/gpt-oss-120b",++ "pricing": {}++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "data": [++ {++ "id": "gpt-oss-120b",++ "pricing": {}++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,18 @@++{++ "data": [++ {++ "id": "Qwen/Qwen3-32B-TEE",++ "pricing": {++ "input": 0.104,++ "output": 0.416++ }++ },++ {++ "id": "unsloth/Mistral-Nemo-Instruct-2407-TEE",++ "pricing": {++ "input": 0.0245,++ "output": 0.0978++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "data": [++ {++ "id": "accounts/fireworks/models/gpt-oss-120b",++ "pricing": {++ "input": 0.15,++ "output": 0.6++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,12 @@++{++ "data": [++ {++ "id": "openai/gpt-oss-120b",++ "pricing": {++ "input": 0.15,++ "cached": 0.075,++ "output": 0.6++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "data": [++ {++ "id": "openai/gpt-oss-120b",++ "pricing": {}++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "data": [++ {++ "id": "Qwen/Qwen3-30B-A3B-Instruct-2507",++ "pricing": {++ "input": 0.1,++ "output": 0.3++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "data": [++ {++ "id": "gpt-oss-120b",++ "pricing": {++ "input": 0.22,++ "output": 0.59++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "data": [++ {++ "id": "Qwen/Qwen3-32B",++ "pricing": {}++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "data": [++ {++ "id": "openai/gpt-oss-120b",++ "pricing": {++ "input": 0.15,++ "output": 0.6++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "data": [++ {++ "id": "abliterated",++ "pricing": {}++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "data": [++ {++ "id": "venice-uncensored-1-2",++ "pricing": {++ "input": 0.2,++ "output": 0.9++ }++ }++ ]++}@@@ -8,10 +8,23 @@ const http = require('http');const { suite } = require('./helpers');const { createRegistry } = require('../server/registry');const catalog = require('../server/providers/catalog');++const modelCatalog = require('../server/providers/model-catalog');const { testDb } = require('./db');const t = suite('model-catalog');++t.test('owner override and provider markup publish the same retail rate the router bills', () => {++ const row = { provider_key: 'groq', model_key: 'openai/gpt-oss-120b', display_name: 'GPT-OSS 120B', type: 'chat', status: 'active',++ context_window: 131072, max_output: 65536, cost: { in_per_mtok: 0.15, cached_per_mtok: 0.075, out_per_mtok: 0.6 },++ supports: { json: true, tools: true, streaming: true, vision: false }, metadata: { owner_override: { cost: { in_per_mtok: 0.2 }, reason: 'Owner checked negotiated rate' } } };++ const provider = { key: 'groq', status: 'active', metadata: { markup_percent: 10 } };++ const entry = modelCatalog.entryOf(row, provider);++ assert.ok(Math.abs(entry.cost.in_per_mtok - 0.22) < 1e-12);++ assert.ok(Math.abs(entry.cost.cached_per_mtok - 0.0825) < 1e-12);++ assert.ok(Math.abs(entry.cost.out_per_mtok - 0.66) < 1e-12);++ assert.strictEqual(entry.retail_rate.input_nano_usd_per_m, '220000000');++});++function server(handler) {return new Promise((resolve) => {const seen = [];@@@ -21,6 +21,7 @@ const FILE = {deepseek: 'deepseek.md', qwen: 'qwen.html', kimi: 'kimi.html', glm: 'glm.md', minimax: 'minimax.md',deepinfra: 'deepinfra.json', novita: 'novita.json', featherless: 'featherless.json',refuseless: 'refuseless.md', abliteration: 'abliteration.md', openrouter: 'openrouter.json',++ ...Object.fromEntries(['venice', 'abliteratedmodels', 'uplinkswitch', 'chutes', 'siliconflow', 'nebius', 'together', 'fireworks', 'groq', 'cerebras', 'sambanova', 'baseten', 'hyperbolic'].map((key) => [key, `${key}.json`])),};const read = (name) => fs.readFileSync(path.join(FIXTURES, name), 'utf8');const json = (name) => JSON.parse(read(name));@@@ -29,7 +30,7 @@ const json = (name) => JSON.parse(read(name));const BY_URL = new Map(SOURCES.map((s) => [s.url, FILE[s.key]]));async function recorded({ url }) {const file = BY_URL.get(url);-- if (!file) return { status: 404, headers: {}, body: '' };++ if (!file || !fs.existsSync(path.join(FIXTURES, file))) return { status: 404, headers: {}, body: '' };return { status: 200, headers: {}, body: read(file) };}@@@ -61,6 +62,16 @@ t.test('every provider listing is read in its own shape', async () => {assert.deepStrictEqual(parsers.featherless(json('featherless.json'))['qwen/qwen3-14b'], { in: 0.12, cached: null, out: 0.24 });// A provider that publishes no price at all: the price stays unknown, it is never guessed.assert.deepStrictEqual(parsers.refuseless(read('refuseless.md'))['deepseek-v4.1-flash-abliterated'], { in: null, cached: null, out: null });++ assert.deepStrictEqual(parsers.compatibleModels(json('venice.json'))['venice-uncensored-1-2'], { in: 0.2, cached: null, out: 0.9 });++ assert.deepStrictEqual(parsers.compatibleModels(json('abliteratedmodels.json')).uncensored, { in: 0.15, cached: null, out: 0.5 });++ assert.deepStrictEqual(parsers.compatibleModels(json('chutes.json'))['qwen/qwen3-32b-tee'], { in: 0.104, cached: null, out: 0.416 });++ assert.deepStrictEqual(parsers.compatibleModels(json('nebius.json'))['qwen/qwen3-30b-a3b-instruct-2507'], { in: 0.1, cached: null, out: 0.3 });++ assert.deepStrictEqual(parsers.compatibleModels(json('fireworks.json'))['accounts/fireworks/models/gpt-oss-120b'], { in: 0.15, cached: null, out: 0.6 });++ assert.deepStrictEqual(parsers.compatibleModels(json('together.json'))['openai/gpt-oss-120b'], { in: 0.15, cached: null, out: 0.6 });++ assert.deepStrictEqual(parsers.compatibleModels(json('groq.json'))['openai/gpt-oss-120b'], { in: 0.15, cached: 0.075, out: 0.6 });++ assert.deepStrictEqual(parsers.compatibleModels(json('sambanova.json'))['gpt-oss-120b'], { in: 0.22, cached: null, out: 0.59 });++ for (const key of ['uplinkswitch', 'siliconflow', 'cerebras', 'baseten', 'hyperbolic'])++ assert.deepStrictEqual(parsers.compatibleModels(json(`${key}.json`)), {}, `${key} exposes no price in its recorded listing`);});t.test('the recorder keeps the rows the catalogue knows, and reads nothing of its own', async () => {
@@ -45,9 +45,50 @@
@@ -45,9 +45,50 @@
| Prime Intellect | REST / custom | `http` | No | `UNVERIFIED` | Terms URL returned 404. | https://www.primeintellect.ai/terms (404) | 2026-11-03 |
| Prime Intellect | REST / custom | `http` | No | `UNVERIFIED` | Terms URL returned 404. | https://www.primeintellect.ai/terms (404) | 2026-11-03 |
| Self-hosted vLLM / SGLang / llama.cpp / Ollama | OpenAI-compatible | `openai` / `http` | N/A | `OWN_DEPLOYMENT` | Open-source runtimes deployed on OpenVibe-owned or customer-authorized infrastructure; no upstream resale constraint. | Various OS licenses | 2026-11-03 |
| Self-hosted vLLM / SGLang / llama.cpp / Ollama | OpenAI-compatible | `openai` / `http` | N/A | `OWN_DEPLOYMENT` | Open-source runtimes deployed on OpenVibe-owned or customer-authorized infrastructure; no upstream resale constraint. | Various OS licenses | 2026-11-03 |
+## Pool presets added 2026-11-09
+
+These presets are seeded disabled in practice until the owner adds a provider key. Commercial access remains `UNVERIFIED` until an executed agreement explicitly permits managed resale; Together stays `RESTRICTED` because its current terms prohibit resale without authorization. `null` privacy facts mean the provider did not state them on the pages checked. A listed price is the provider's own published list price per 1M tokens, not an aggregator quote.
+
+| Provider | Base URL | Privacy facts confirmed | Access default | Own provider listing / terms |
+|---|---|---|---|---|
+| Venice AI | `https://api.venice.ai/api/v1` | Venice Uncensored 1.2: private, zero retention, not used for training | `UNVERIFIED` | [Model page and price](https://docs.venice.ai/models/venice-uncensored-1-2) |
+| abliteratedmodels.cc | `https://api.abliteratedmodels.cc/v1` | Retention and training unstated; its own site says lawful adult and fictional content is allowed, with minor sexual content excluded | `UNVERIFIED` | [Provider model and pricing](https://abliteratedmodels.cc/) |
+| UplinkSwitch | `https://api.uplinkswitch.ai/v1` | Adult content between fictional adults permitted for 18+ where legal; no retention facts confirmed | `UNVERIFIED` | [Acceptable use](https://uplinkswitch.ai/acceptable-use) |
+| Chutes | `https://llm.chutes.ai/v1` | Retention/training unstated; TEE flag is not treated as a retention promise | `UNVERIFIED` | [Pricing](https://www.chutes.ai/pricing) |
+| SiliconFlow | `https://api.siliconflow.com/v1` | Retention/training unstated | `UNVERIFIED` | [Provider docs](https://docs.siliconflow.com/) |
+| Nebius Token Factory | `https://api.tokenfactory.nebius.com/v1` | Retention/training unstated | `UNVERIFIED` | [Model pricing](https://nebius.com/services/token-factory/models/qwen-models-inference) |
+| Together AI | `https://api.together.xyz/v1` | Retention/training unstated here; terms prohibit resale absent authorization | `RESTRICTED` | [Terms](https://www.together.ai/terms-of-service) |
+| Fireworks AI | `https://api.fireworks.ai/inference/v1` | Retention/training unstated | `UNVERIFIED` | [Model catalog](https://fireworks.ai/models) |
+| Groq | `https://api.groq.com/openai/v1` | Retention/training unstated | `UNVERIFIED` | [Model pricing and limits](https://console.groq.com/docs/model/openai/gpt-oss-120b) |
+| Cerebras | `https://api.cerebras.ai/v1` | Retention/training unstated | `UNVERIFIED` | [Public model catalog](https://api.cerebras.ai/public/v1/models) |
+| SambaNova | `https://api.sambanova.ai/v1` | Retention/training unstated | `UNVERIFIED` | [Pricing](https://cloud.sambanova.ai/plans/pricing) |
+| Baseten | `https://inference.baseten.co/v1` | Retention/training unstated | `UNVERIFIED` | [Pricing](https://www.baseten.co/pricing/) |
+| Hyperbolic | `https://api.hyperbolic.xyz/v1` | Retention/training unstated | `UNVERIFIED` | [Inference pricing](https://docs.hyperbolic.xyz/docs/hyperbolic-ai-inference-pricing) |
+
+## Cheapest confirmed open-weight prices
+
+These are the lowest prices confirmed among the listed provider-owned source pages; an unpriced provider is not assumed to cost more. Cached prices are `—` when the provider does not publish one.
+
+| Model / tier | Cheapest confirmed provider | Input / cached / output USD per 1M tokens | Context | Provider source | Checked |
+|---|---|---:|---:|---|---|
+| GPT-OSS 120B | DeepInfra | $0.037 / — / $0.17 | 131,072 | [DeepInfra model list](https://api.deepinfra.com/v1/models) | 2026-11-06 |
+| DeepSeek V4 Flash | DeepInfra | $0.09 / $0.018 / $0.18 | 1,048,576 | [DeepInfra model list](https://api.deepinfra.com/v1/models) | 2026-11-06 |
+| DeepSeek V4.1 Flash 1017 | DeepInfra | $0.20 / $0.006 / $0.60 | 1,048,576 | [DeepInfra model list](https://api.deepinfra.com/v1/models) | 2026-11-06 |
+| Qwen3.8-Max | DeepInfra | $1.65 / $0.206 / $4.951 | 256,000 | [DeepInfra model list](https://api.deepinfra.com/v1/models) | 2026-11-06 |
+| Qwen3-14B | DeepInfra or Featherless (tie) | $0.12 / — / $0.24 | 32,768 | [DeepInfra model list](https://api.deepinfra.com/v1/models), [Featherless model list](https://api.featherless.ai/v1/models) | 2026-11-06 |
+| Qwen3-30B-A3B-Instruct-2507 | Nebius Token Factory | $0.10 / — / $0.30 | 262,144 | [Nebius model pricing](https://nebius.com/services/token-factory/models/qwen-models-inference) | 2026-11-09 |
+| Qwen3-32B TEE | Chutes | $0.104 / — / $0.416 | 40,000 | [Chutes pricing](https://www.chutes.ai/pricing) | 2026-11-09 |
+| Llama 3.1 8B Instruct | Featherless | $0.05 / — / $0.08 | 32,768 | [Featherless model list](https://api.featherless.ai/v1/models) | 2026-11-06 |
+| GLM-5.3 | DeepInfra | $0.90 / $0.20 / $4.00 | 1,048,576 | [DeepInfra model list](https://api.deepinfra.com/v1/models) | 2026-11-06 |
+| Kimi K3.1 Pro | Kimi or Novita (tie) | $2.00 / $0.20 / $10.00 | 1,048,576 | [Moonshot pricing](https://platform.kimi.ai/docs/pricing/chat), [Novita model list](https://api.novita.ai/v3/openai/models) | 2026-11-06 |
+| Mistral Nemo Instruct 2407 TEE | Chutes | $0.0245 / — / $0.0978 | 131,072 | [Chutes pricing](https://www.chutes.ai/pricing) | 2026-11-09 |
+| Qwen3.8 Flash | Alibaba Model Studio | $0.15 / — / $0.47 | 1,048,576 | [Alibaba pricing](https://www.alibabacloud.com/help/en/model-studio/model-pricing) | 2026-11-06 |
+| Uncensored API (`uncensored`) | abliteratedmodels.cc | $0.15 / — / $0.50 | 100,000 | [Provider pricing](https://abliteratedmodels.cc/) | 2026-11-09 |
+| Venice Uncensored 1.2 | Venice AI | $0.20 / — / $0.90 | 131,072 | [Venice model page](https://docs.venice.ai/models/venice-uncensored-1-2) | 2026-11-09 |
+
## Notes
## Notes
1. **BYOK in OpenVibe.AI today**: `server/credentials.js` and `server/console/presets.js` support stored credentials for `anthropic`, `openai`, `gemini`, `deepseek`, `mistral`, `xai`, `groq`, `cerebras`, `together`, `fireworks`, `openrouter`, `minimax`, and `custom`. Any OpenAI-compatible provider can also be reached via the `openai` adapter with a custom `base_url` and `x_openvibe.provider` / credential.
1. **BYOK in OpenVibe.AI today**: `server/credentials.js` and `server/console/presets.js` support stored credentials for `anthropic`, `openai`, `gemini`, `deepseek`, `mistral`, `xai`, `groq`, `cerebras`, `together`, `fireworks`, `openrouter`, `minimax`, and `custom`. Any OpenAI-compatible provider can also be reached via the `openai` adapter with a custom `base_url` and `x_openvibe.provider` / credential.
2. **Managed resale is blocked by default**. Only `OWN_DEPLOYMENT` and `AUTHORIZED_WHOLESALE` (with executed agreement) may be used for paid managed routes. `CUSTOMER_BYOK` is allowed where the customer is billed directly by the upstream.
2. **Managed resale is blocked by default**. Only `OWN_DEPLOYMENT` and `AUTHORIZED_WHOLESALE` (with executed agreement) may be used for paid managed routes. `CUSTOMER_BYOK` is allowed where the customer is billed directly by the upstream.
3. **Unknown providers** are listed as `UNVERIFIED`. Before enabling any paid forwarding, obtain current terms, an executed commercial agreement, and legal review.
3. **Unknown providers** are listed as `UNVERIFIED`. Before enabling any paid forwarding, obtain current terms, an executed commercial agreement, and legal review.
-4. Prices are **not** recorded in this matrix per PLAN instructions; they must be obtained as live quotes from official sources and versioned in `platform.rate-card@1` before use.
+4. The comparison above records the lowest confirmed list price from provider-owned pages; these are not resale quotes. Managed prices still follow the accepted model price plus the owner-set markup, and commercial eligibility stays gated by `access_mode`.
@@ -78,7 +78,21 @@ function adminRouter({ config = {}, db, registry, pool, quotas, cache, runs, aut
@@ -78,7 +78,21 @@ function adminRouter({ config = {}, db, registry, pool, quotas, cache, runs, aut
if (!String(b.base_url || '').trim()) throw new AiError(422, 'ai.invalid', 'a provider needs a base URL');
if (!String(b.base_url || '').trim()) throw new AiError(422, 'ai.invalid', 'a provider needs a base URL');
await settings.providerKeys.setOrigin(prev.key, b.base_url, { actor: { type: 'service', id: 'ai' }, reason: 'provider base_url changed' });
await settings.providerKeys.setOrigin(prev.key, b.base_url, { actor: { type: 'service', id: 'ai' }, reason: 'provider base_url changed' });
}
}
- const provider = await registry.upsertProvider({ ...b, key: req.params.key }, { ...actor(req), origin: 'admin' });
+ const access = ['OWN_DEPLOYMENT', 'AUTHORIZED_WHOLESALE', 'CUSTOMER_BYOK', 'RESTRICTED', 'UNVERIFIED'];
+ if (b.provider_access !== undefined && !access.includes(b.provider_access)) throw new AiError(422, 'ai.invalid', 'provider_access is not a supported access mode');
+ if (b.status !== undefined && !['active', 'disabled'].includes(b.status)) throw new AiError(422, 'ai.invalid', 'status must be active or disabled');
+ const meta = { ...(prev.metadata || {}), ...(b.metadata || {}) };
+ if (b.uncensored !== undefined) {
+ if (typeof b.uncensored !== 'boolean') throw new AiError(422, 'ai.invalid', 'uncensored must be a boolean');
+ meta.uncensored = b.uncensored;
+ }
+ if (b.preference_order !== undefined && b.priority === undefined) b.priority = b.preference_order;
+ if (b.markup_percent !== undefined) {
+ const markup = Number(b.markup_percent);
+ if (!Number.isFinite(markup) || markup < 0 || markup > 500) throw new AiError(422, 'ai.invalid', 'markup_percent must be between 0 and 500');
+ meta.markup_percent = markup;
+ }
+ const provider = await registry.upsertProvider({ ...b, metadata: meta, key: req.params.key }, { ...actor(req), origin: 'admin' });
const answer = { provider: await view(provider) };
const answer = { provider: await view(provider) };
if (moved && String(provider.secret_ref || '').startsWith('vault:')) {
if (moved && String(provider.secret_ref || '').startsWith('vault:')) {
answer.key_reentry_required = true;
answer.key_reentry_required = true;
@@ -96,7 +110,25 @@ function adminRouter({ config = {}, db, registry, pool, quotas, cache, runs, aut
@@ -96,7 +110,25 @@ function adminRouter({ config = {}, db, registry, pool, quotas, cache, runs, aut
r.post('/api/v1/models', provWrite, wrap(async (req, res) => { res.status(201); return { model: await registry.upsertModel(body(req), actor(req)) }; }));
r.post('/api/v1/models', provWrite, wrap(async (req, res) => { res.status(201); return { model: await registry.upsertModel(body(req), actor(req)) }; }));
r.patch('/api/v1/models/:provider/:model', provWrite, wrap(async (req) => {
r.patch('/api/v1/models/:provider/:model', provWrite, wrap(async (req) => {
if (!await registry.getModel(req.params.provider, req.params.model)) throw new AiError(404, 'ai.not_found', 'no such model');
if (!await registry.getModel(req.params.provider, req.params.model)) throw new AiError(404, 'ai.not_found', 'no such model');
- return { model: await registry.upsertModel({ ...body(req), provider_key: req.params.provider, model_key: req.params.model }, actor(req)) };
+ const b = body(req);
+ const prev = await registry.getModel(req.params.provider, req.params.model);
+ const metadata = { ...(prev.metadata || {}), ...(b.metadata || {}) };
+ if (b.owner_override !== undefined) {
+ const override = b.owner_override;
+ if (override === null) {
+ delete metadata.owner_override;
+ } else {
+ if (!override || typeof override !== 'object' || !String(override.reason || '').trim()) throw new AiError(422, 'ai.invalid', 'owner_override requires a reason');
+ const supplied = override.cost;
+ const fields = ['in_per_mtok', 'out_per_mtok', 'cached_per_mtok'];
+ if (!supplied || typeof supplied !== 'object' || !fields.some((k) => supplied[k] != null) ||
+ fields.some((k) => supplied[k] != null && (!Number.isFinite(Number(supplied[k])) || Number(supplied[k]) < 0)))
+ throw new AiError(422, 'ai.invalid', 'owner_override cost fields must be non-negative numbers or null');
+ const price = Object.fromEntries(fields.filter((k) => supplied[k] != null).map((k) => [k, Number(supplied[k])]));
+ metadata.owner_override = { cost: price, reason: String(override.reason).trim().slice(0, 500), updated_at: new Date().toISOString() };
+ }
+ }
+ return { model: await registry.upsertModel({ ...b, metadata, provider_key: req.params.provider, model_key: req.params.model }, actor(req)) };
}));
}));
// Versioned definitions
// Versioned definitions
@@ -32,15 +32,23 @@ function consoleDataRouter({ db, registry, pool, quotas, cache, runs, ops, auth,
@@ -32,15 +32,23 @@ function consoleDataRouter({ db, registry, pool, quotas, cache, runs, ops, auth,
return { status: await ops.status(), counts24: await q.statusCounts(db, since), failures24: await q.failures(db, since), queue: runs.stats(), cache: await cache.stats() };
return { status: await ops.status(), counts24: await q.statusCounts(db, since), failures24: await q.failures(db, since), queue: runs.stats(), cache: await cache.stats() };
}
}
if (section === 'providers') {
if (section === 'providers') {
- const rows = await Promise.all((await registry.listProviders()).map(ops.providerView));
+ const rows = (await Promise.all((await registry.listProviders()).map(ops.providerView))).map((p) => ({
+ ...p, key_present: p.credentials === 'configured', preference_order: p.priority,
+ markup_percent: Number(p.metadata && p.metadata.markup_percent) || 0,
+ uncensored: Boolean(p.metadata && p.metadata.uncensored),
+ }));
+ const poolProviders = require('../providers/pool-providers').PROVIDERS;
const estimates = creditGrants ? await Promise.all(['anthropic', 'openai'].map(async (provider) => ({
const estimates = creditGrants ? await Promise.all(['anthropic', 'openai'].map(async (provider) => ({
provider, grants: await creditGrants.list(provider), estimate: creditGrants.values().find((x) => x.provider === provider) || null,
provider, grants: await creditGrants.list(provider), estimate: creditGrants.values().find((x) => x.provider === provider) || null,
preference: await creditGrants.preference(provider),
preference: await creditGrants.preference(provider),
}))) : [];
}))) : [];
const cookies = sessionCookies ? await sessionCookies.list('operator', 'owner') : [];
const cookies = sessionCookies ? await sessionCookies.list('operator', 'owner') : [];
const balances = sessionCookies ? await Promise.all(cookies.map((c) => sessionCookies.balance('operator', 'owner', c.id))) : [];
const balances = sessionCookies ? await Promise.all(cookies.map((c) => sessionCookies.balance('operator', 'owner', c.id))) : [];
- return { providers: rows, models: await registry.listModels(), credit_estimates: estimates, session_credentials: cookies, session_balances: balances };
+ return { providers: rows, models: (await registry.listModels()).map((m) => ({ ...m, accepted_price: m.cost,
+ owner_override: m.metadata && m.metadata.owner_override || null })), presets: poolProviders.map((p) => ({ key: p.key, display_name: p.display_name, base_url: p.base_url, default_model: p.default_model, provider_access: p.provider_access, uncensored: Boolean(p.uncensored), models: p.models })), credit_estimates: estimates, session_credentials: cookies, session_balances: balances };
}
}
+ if (section === 'models') return { models: (await registry.listModels()).map((m) => ({ ...m, accepted_price: m.cost,
+ owner_override: m.metadata && m.metadata.owner_override || null })) };
if (section === 'runs') {
if (section === 'runs') {
const { filters, form, bad } = q.parseRunFilters(req.query);
const { filters, form, bad } = q.parseRunFilters(req.query);
const list = await q.listRuns(db, filters);
const list = await q.listRuns(db, filters);
@@ -490,6 +490,19 @@ function openrouter(json) {
@@ -490,6 +490,19 @@ function openrouter(json) {
return out;
return out;
}
}
+/** OpenAI-compatible providers' public model listings, when the listing includes provider pricing metadata. */
+function compatibleModels(json) {
+ const out = {};
+ for (const m of (json && (json.data || json.models)) || []) {
+ const p = (m && (m.pricing || m.metadata && m.metadata.pricing)) || null;
+ if (!p || !m.id) continue;
+ const pick = (...keys) => { for (const k of keys) if (p[k] != null) return num(p[k]); return null; };
+ const entry = { in: pick('input', 'prompt', 'input_tokens'), cached: pick('cached', 'cache_read', 'cached_input', 'cache_read_tokens'), out: pick('output', 'completion', 'output_tokens') };
+ if (Object.values(entry).some((v) => v != null)) out[String(m.id).trim().toLowerCase()] = entry;
+ }
+ return out;
+}
+
/**
/**
* Refuseless publishes its model list but no price at all (its pricing page answers a bot check). The parser
* Refuseless publishes its model list but no price at all (its pricing page answers a bot check). The parser
* reads the list and finds no price, so the price book leaves every price of that provider alone.
* reads the list and finds no price, so the price book leaves every price of that provider alone.
@@ -521,6 +534,6 @@ function refuseless(text) {
@@ -521,6 +534,6 @@ function refuseless(text) {
module.exports = {
module.exports = {
leaf, num, money, price, markdownTables, htmlText, columnOf, normHead, leadingId, trailingId,
leaf, num, money, price, markdownTables, htmlText, columnOf, normHead, leadingId, trailingId,
- glm, abliteration, minimax, deepseek, qwen, kimi, deepinfra, novita, featherless, openrouter, refuseless,
+ glm, abliteration, minimax, deepseek, qwen, kimi, deepinfra, novita, featherless, openrouter, refuseless, compatibleModels,
peakHours, weekdaySet, complementHours, offPeakBlocks, deepseekOffPeakBlocks,
peakHours, weekdaySet, complementHours, offPeakBlocks, deepseekOffPeakBlocks,
};
};
@@ -108,6 +108,21 @@ const SOURCES = [
@@ -108,6 +108,21 @@ const SOURCES = [
kind: 'json',
kind: 'json',
parse: parsers.openrouter,
parse: parsers.openrouter,
},
},
+ ...[
+ ['venice', 'https://api.venice.ai/api/v1/models'],
+ ['abliteratedmodels', 'https://api.abliteratedmodels.cc/v1/models'],
+ ['uplinkswitch', 'https://api.uplinkswitch.ai/v1/models'],
+ ['chutes', 'https://llm.chutes.ai/v1/models'],
+ ['siliconflow', 'https://api.siliconflow.com/v1/models'],
+ ['nebius', 'https://api.tokenfactory.nebius.com/v1/models'],
+ ['together', 'https://api.together.xyz/v1/models'],
+ ['fireworks', 'https://api.fireworks.ai/inference/v1/models'],
+ ['groq', 'https://api.groq.com/openai/v1/models'],
+ ['cerebras', 'https://api.cerebras.ai/public/v1/models'],
+ ['sambanova', 'https://api.sambanova.ai/v1/models'],
+ ['baseten', 'https://inference.baseten.co/v1/models'],
+ ['hyperbolic', 'https://api.hyperbolic.xyz/v1/models'],
+ ].map(([key, url]) => ({ key, provider: key, role: 'own', url, kind: 'json', parse: parsers.compatibleModels })),
];
];
/** The listings that may change a price (the cross-reference is not one of them). */
/** The listings that may change a price (the cross-reference is not one of them). */
@@ -325,7 +325,13 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no
@@ -325,7 +325,13 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no
const p = await registry.getProvider(providerKey);
const p = await registry.getProvider(providerKey);
if (!p || isFreeProvider(p)) return null;
if (!p || isFreeProvider(p)) return null;
const row = model ? await registry.getModel(providerKey, model) : null;
const row = model ? await registry.getModel(providerKey, model) : null;
- return rates.buildCards({ providerKey, model: model || null, row, pricing: config.pricing });
+ const markup = Number(p.metadata && p.metadata.markup_percent) || 0;
+ const override = row && row.metadata && row.metadata.owner_override && row.metadata.owner_override.cost;
+ const baseCost = row ? { ...row.cost, ...(override || {}) } : null;
+ const billable = row && override ? { ...row, cost: baseCost } : row;
+ const cards = rates.buildCards({ providerKey, model: model || null, row: billable, pricing: config.pricing });
+ if (!cards || markup <= 0) return cards;
+ return Object.fromEntries(Object.entries(cards).map(([kind, card]) => [kind, { ...card, unit_price_usd: Number(card.unit_price_usd) * (1 + markup / 100) }]));
}
}
async function priceFor(providerKey, model) {
async function priceFor(providerKey, model) {
const cards = await rateCardsFor(providerKey, model);
const cards = await rateCardsFor(providerKey, model);
@@ -115,10 +115,21 @@ function availabilityOf(row, provider) {
@@ -115,10 +115,21 @@ function availabilityOf(row, provider) {
return 'available';
return 'available';
}
}
+/** Owner price override and provider markup are the final retail rate, while the stored row remains the accepted base price. */
+function effectiveCost(row, provider) {
+ const override = row && row.metadata && row.metadata.owner_override && row.metadata.owner_override.cost;
+ const cost = { ...((row && row.cost) || {}), ...(override || {}) };
+ const markup = Number(provider && provider.metadata && provider.metadata.markup_percent) || 0;
+ if (markup > 0) for (const key of ['in_per_mtok', 'out_per_mtok', 'cached_per_mtok', 'cache_write_per_mtok'])
+ if (cost[key] != null) cost[key] = Number(cost[key]) * (1 + markup / 100);
+ return cost;
+}
+
/** One registry model as an ai.model@1 catalog entry. Legacy fields stay for readers of the old shape. */
/** One registry model as an ai.model@1 catalog entry. Legacy fields stay for readers of the old shape. */
function entryOf(row, provider) {
function entryOf(row, provider) {
const meta = (row && row.metadata) || {};
const meta = (row && row.metadata) || {};
const artifact = artifactOf(row, provider);
const artifact = artifactOf(row, provider);
+ const cost = effectiveCost(row, provider);
const out = {
const out = {
provider_key: row.provider_key,
provider_key: row.provider_key,
model_key: row.model_key,
model_key: row.model_key,
@@ -129,10 +140,10 @@ function entryOf(row, provider) {
@@ -129,10 +140,10 @@ function entryOf(row, provider) {
context_window: row.context_window == null ? null : Number(row.context_window),
context_window: row.context_window == null ? null : Number(row.context_window),
max_output: row.max_output == null ? null : Number(row.max_output),
max_output: row.max_output == null ? null : Number(row.max_output),
cost: {
cost: {
- in_per_mtok: row.cost.in_per_mtok == null ? null : Number(row.cost.in_per_mtok),
- out_per_mtok: row.cost.out_per_mtok == null ? null : Number(row.cost.out_per_mtok),
- cached_per_mtok: row.cost.cached_per_mtok == null ? null : Number(row.cost.cached_per_mtok),
- ...(row.cost.cache_write_per_mtok == null ? {} : { cache_write_per_mtok: Number(row.cost.cache_write_per_mtok) }),
+ in_per_mtok: cost.in_per_mtok == null ? null : Number(cost.in_per_mtok),
+ out_per_mtok: cost.out_per_mtok == null ? null : Number(cost.out_per_mtok),
+ cached_per_mtok: cost.cached_per_mtok == null ? null : Number(cost.cached_per_mtok),
+ ...(cost.cache_write_per_mtok == null ? {} : { cache_write_per_mtok: Number(cost.cache_write_per_mtok) }),
},
},
supports: { ...row.supports },
supports: { ...row.supports },
metadata: meta,
metadata: meta,
@@ -152,7 +163,7 @@ function entryOf(row, provider) {
@@ -152,7 +163,7 @@ function entryOf(row, provider) {
if (meta.license) out.license = meta.license;
if (meta.license) out.license = meta.license;
}
}
if (Number(row.context_window) > 0) out.context = Number(row.context_window);
if (Number(row.context_window) > 0) out.context = Number(row.context_window);
- const retail = retailRate(row);
+ const retail = retailRate({ ...row, cost });
if (retail) out.retail_rate = retail;
if (retail) out.retail_rate = retail;
return out;
return out;
}
}
@@ -45,9 +45,10 @@
@@ -45,9 +45,10 @@
/** The day the prices, the privacy labels and the model lists below were read. */
/** The day the prices, the privacy labels and the model lists below were read. */
const CHECKED = '2026-11-06';
const CHECKED = '2026-11-06';
+const NEW_CHECKED = '2026-11-09';
/** The keys of the ten providers, in the order the router should prefer them when the price is equal. */
/** The keys of the ten providers, in the order the router should prefer them when the price is equal. */
-const PROVIDER_KEYS = ['qwen', 'kimi', 'glm', 'minimax', 'deepinfra', 'novita', 'featherless', 'refuseless', 'abliteration', 'openrouter'];
+const PROVIDER_KEYS = ['qwen', 'kimi', 'glm', 'minimax', 'deepinfra', 'novita', 'featherless', 'refuseless', 'abliteration', 'openrouter', 'venice', 'abliteratedmodels', 'uplinkswitch', 'chutes', 'siliconflow', 'nebius', 'together', 'fireworks', 'groq', 'cerebras', 'sambanova', 'baseten', 'hyperbolic'];
/**
/**
* What a provider says it does with a prompt.
* What a provider says it does with a prompt.
@@ -635,6 +636,42 @@ const PROVIDERS = [
@@ -635,6 +636,42 @@ const PROVIDERS = [
// the key. No model is seeded here: a price we did not read from OpenRouter is not one of ours to publish.
// the key. No model is seeded here: a price we did not read from OpenRouter is not one of ours to publish.
models: [],
models: [],
},
},
+ ...[
+ ['venice', 'Venice AI', 'https://api.venice.ai/api/v1', 'venice-uncensored-1-2', 'https://docs.venice.ai/overview/pricing', 'PRIVATE', 'UNVERIFIED', { in: 0.20, cached: null, out: 0.90 }, 131072, true],
+ ['abliteratedmodels', 'abliteratedmodels.cc', 'https://api.abliteratedmodels.cc/v1', 'uncensored', 'https://abliteratedmodels.cc/es/', 'UNKNOWN', 'UNVERIFIED', { in: 0.15, cached: null, out: 0.50 }, 100000, true],
+ ['uplinkswitch', 'UplinkSwitch', 'https://api.uplinkswitch.ai/v1', 'abliterated', 'https://uplinkswitch.ai/acceptable-use', 'UNKNOWN', 'UNVERIFIED', { in: null, cached: null, out: null }, null, true],
+ ['chutes', 'Chutes', 'https://llm.chutes.ai/v1', 'Qwen/Qwen3-32B-TEE', 'https://www.chutes.ai/pricing', 'UNKNOWN', 'UNVERIFIED', { in: 0.104, cached: null, out: 0.416 }, 40960, false],
+ ['siliconflow', 'SiliconFlow', 'https://api.siliconflow.com/v1', 'Qwen/Qwen3-32B', 'https://docs.siliconflow.com/', 'UNKNOWN', 'UNVERIFIED', { in: null, cached: null, out: null }, null, false],
+ ['nebius', 'Nebius Token Factory', 'https://api.tokenfactory.nebius.com/v1', 'Qwen/Qwen3-30B-A3B-Instruct-2507', 'https://nebius.com/services/token-factory/models/qwen-models-inference', 'UNKNOWN', 'UNVERIFIED', { in: 0.10, cached: null, out: 0.30 }, 262144, false],
+ ['together', 'Together AI', 'https://api.together.xyz/v1', 'openai/gpt-oss-120b', 'https://www.together.ai/pricing', 'UNKNOWN', 'RESTRICTED', { in: 0.15, cached: null, out: 0.60 }, 131072, false],
+ ['fireworks', 'Fireworks AI', 'https://api.fireworks.ai/inference/v1', 'accounts/fireworks/models/gpt-oss-120b', 'https://fireworks.ai/models', 'UNKNOWN', 'UNVERIFIED', { in: 0.15, cached: null, out: 0.60 }, 131072, false],
+ ['groq', 'Groq', 'https://api.groq.com/openai/v1', 'openai/gpt-oss-120b', 'https://console.groq.com/docs/model/openai/gpt-oss-120b', 'UNKNOWN', 'UNVERIFIED', { in: 0.15, cached: 0.075, out: 0.60 }, 131072, false],
+ ['cerebras', 'Cerebras', 'https://api.cerebras.ai/v1', 'gpt-oss-120b', 'https://api.cerebras.ai/public/v1/models', 'UNKNOWN', 'UNVERIFIED', { in: null, cached: null, out: null }, 131072, false],
+ ['sambanova', 'SambaNova', 'https://api.sambanova.ai/v1', 'gpt-oss-120b', 'https://cloud.sambanova.ai/plans/pricing', 'UNKNOWN', 'UNVERIFIED', { in: 0.22, cached: null, out: 0.59 }, 131072, false],
+ ['baseten', 'Baseten', 'https://inference.baseten.co/v1', 'openai/gpt-oss-120b', 'https://www.baseten.co/pricing/', 'UNKNOWN', 'UNVERIFIED', { in: null, cached: null, out: null }, 131072, false],
+ ['hyperbolic', 'Hyperbolic', 'https://api.hyperbolic.xyz/v1', 'openai/gpt-oss-120b', 'https://docs.hyperbolic.xyz/docs/hyperbolic-ai-inference-pricing', 'UNKNOWN', 'UNVERIFIED', { in: null, cached: null, out: null }, 131072, false],
+ ].map(([key, display_name, base_url, model_key, source, privacyLabel, provider_access, prices, context_window, uncensored]) => {
+ const privateTier = privacyLabel === 'PRIVATE';
+ return {
+ key, display_name, base_url, default_model: model_key, timeout_ms: 60000,
+ capabilities: ['chat', 'generate', 'summarize', 'classify', 'extract', 'enrich', 'json', 'tools'],
+ provider_access, priority: 40, ...(uncensored ? { uncensored: true } : {}),
+ privacy: privacy({ label: privateTier ? 'private' : 'anonymized', terms_url: privateTier ? source : null,
+ wording: privateTier ? 'Venice states prompts and generations are never stored or logged, and are not used to train future models for this model.' : 'Retention and training commitments were not confirmed on the provider page; unknown.' }),
+ data_policy: policy({ trains: privateTier ? false : null, retention: privateTier ? 'none' : null,
+ retention_note: privateTier ? 'zero data retention stated for this model' : null, terms: privateTier ? source : null,
+ resale: 'UNVERIFIED; owner must verify resale rights before enabling managed access' }),
+ models: [{ model_key, display_name: model_key, family: uncensored ? 'abliterated' : key,
+ context_window, cost: { in_per_mtok: prices.in, cached_per_mtok: prices.cached, out_per_mtok: prices.out },
+ source, checked: NEW_CHECKED, supports: { json: true, tools: true, streaming: true, vision: false },
+ quantization: null, refusal_rate: null, ...(uncensored ? { uncensored: true } : {}),
+ note: prices.in == null || prices.out == null ? 'Provider price not confirmed from its own price listing; kept null pending pricebook review.' : null },
+ ...(key === 'chutes' ? [{ model_key: 'unsloth/Mistral-Nemo-Instruct-2407-TEE', display_name: 'Mistral Nemo Instruct 2407 TEE', family: 'mistral', context_window: 131072,
+ cost: { in_per_mtok: 0.0245, cached_per_mtok: null, out_per_mtok: 0.0978 }, source, checked: NEW_CHECKED,
+ supports: { json: true, tools: true, streaming: true, vision: false }, quantization: null, refusal_rate: null }] : []),
+ ],
+ };
+ }),
];
];
const byKey = new Map(PROVIDERS.map((p) => [p.key, p]));
const byKey = new Map(PROVIDERS.map((p) => [p.key, p]));
@@ -116,7 +116,8 @@ function createRegistry(db, { clock = { now: () => Date.now() }, env = process.e
@@ -116,7 +116,8 @@ function createRegistry(db, { clock = { now: () => Date.now() }, env = process.e
timeout_ms = excluded.timeout_ms, priority = excluded.priority, billing_profile = excluded.billing_profile, pool_key = excluded.pool_key,
timeout_ms = excluded.timeout_ms, priority = excluded.priority, billing_profile = excluded.billing_profile, pool_key = excluded.pool_key,
provider_access = excluded.provider_access, metadata = excluded.metadata, origin = excluded.origin, updated_at = excluded.updated_at`)
provider_access = excluded.provider_access, metadata = excluded.metadata, origin = excluded.origin, updated_at = excluded.updated_at`)
.run({ ...p, capabilities: JSON.stringify(p.capabilities), metadata: JSON.stringify(p.metadata), origin, t });
.run({ ...p, capabilities: JSON.stringify(p.capabilities), metadata: JSON.stringify(p.metadata), origin, t });
- await audit(actor, prev ? 'provider.update' : 'provider.create', 'provider', p.key, { trace, metadata: { kind: p.kind, status: p.status, base_url: p.base_url, secret_ref: p.secret_ref, origin } });
+ await audit(actor, prev ? 'provider.update' : 'provider.create', 'provider', p.key, { trace, metadata: { kind: p.kind, status: p.status, base_url: p.base_url, secret_ref: p.secret_ref, origin,
+ provider_access: p.provider_access, priority: p.priority, markup_percent: Number(p.metadata && p.metadata.markup_percent) || 0, uncensored: Boolean(p.metadata && p.metadata.uncensored) } });
return await getProvider(p.key);
return await getProvider(p.key);
}
}
@@ -175,7 +176,8 @@ function createRegistry(db, { clock = { now: () => Date.now() }, env = process.e
@@ -175,7 +176,8 @@ function createRegistry(db, { clock = { now: () => Date.now() }, env = process.e
terms.free_allowance ?? 0, terms.reset_period || 'month', terms.effective_from || null, terms.source || null, terms.verified_at || null,
terms.free_allowance ?? 0, terms.reset_period || 'month', terms.effective_from || null, terms.source || null, terms.verified_at || null,
sup.json === false ? 0 : 1, sup.tools ? 1 : 0, sup.streaming ? 1 : 0, sup.vision ? 1 : 0,
sup.json === false ? 0 : 1, sup.tools ? 1 : 0, sup.streaming ? 1 : 0, sup.vision ? 1 : 0,
JSON.stringify(input.metadata || (prev && prev.metadata) || {}), t, t);
JSON.stringify(input.metadata || (prev && prev.metadata) || {}), t, t);
- await audit(actor, prev ? 'model.update' : 'model.create', 'model', `${input.provider_key}/${input.model_key}`, { trace, metadata: { status, type } });
+ await audit(actor, prev ? 'model.update' : 'model.create', 'model', `${input.provider_key}/${input.model_key}`, { trace, metadata: { status, type,
+ owner_override_reason: input.metadata && input.metadata.owner_override && input.metadata.owner_override.reason || null } });
return await getModel(input.provider_key, input.model_key);
return await getModel(input.provider_key, input.model_key);
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
+'use strict';
+
+const assert = require('assert');
+const express = require('express');
+const { createServer } = require('http');
+const { adminRouter } = require('../server/api/admin');
+
+const state = {
+ provider: { key: 'venice', kind: 'openai', status: 'active', base_url: 'https://api.venice.ai/api/v1', metadata: {}, priority: 40, provider_access: 'UNVERIFIED' },
+ model: { provider_key: 'venice', model_key: 'venice-uncensored-1-2', status: 'active', cost: { in_per_mtok: 0.2, out_per_mtok: 0.9 }, metadata: {} },
+ audit: [],
+};
+const registry = {
+ async getProvider(key) { return key === state.provider.key ? state.provider : null; },
+ async upsertProvider(row, options) { state.provider = { ...state.provider, ...row }; state.audit.push(['provider', row, options]); return state.provider; },
+ async getModel(provider, model) { return provider === state.model.provider_key && model === state.model.model_key ? state.model : null; },
+ async upsertModel(row, actor) { state.model = { ...state.model, ...row }; state.audit.push(['model', row, actor]); return state.model; },
+};
+const app = express();
+app.use(express.json());
+app.use((req, _res, next) => { req.principal = { sub: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPX' }; req.ov = { traceId: 'trace-config-test' }; next(); });
+const auth = { requireCap: () => (_req, _res, next) => next() };
+app.use(adminRouter({ config: {}, db: {}, registry, pool: { stats: {} }, quotas: {}, cache: {}, runs: {}, auth,
+ ops: { async providerView(provider) { return provider; } } }));
+
+(async () => {
+ const server = createServer(app);
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
+ const base = `http://127.0.0.1:${server.address().port}`;
+ try {
+ let response = await fetch(`${base}/api/v1/providers/venice`, { method: 'PATCH', headers: { 'content-type': 'application/json' },
+ body: JSON.stringify({ status: 'disabled', provider_access: 'AUTHORIZED_WHOLESALE', priority: 8, markup_percent: 15, uncensored: true }) });
+ assert.strictEqual(response.status, 200, await response.text());
+ assert.strictEqual(state.provider.status, 'disabled');
+ assert.strictEqual(state.provider.provider_access, 'AUTHORIZED_WHOLESALE');
+ assert.strictEqual(state.provider.priority, 8);
+ assert.strictEqual(state.provider.metadata.markup_percent, 15);
+ assert.strictEqual(state.provider.metadata.uncensored, true);
+ assert.strictEqual(state.audit[0][0], 'provider');
+
+ response = await fetch(`${base}/api/v1/models/venice/venice-uncensored-1-2`, { method: 'PATCH', headers: { 'content-type': 'application/json' },
+ body: JSON.stringify({ owner_override: { cost: { in_per_mtok: 0.25 }, reason: 'Owner verified negotiated price' } }) });
+ assert.strictEqual(response.status, 200, await response.text());
+ assert.strictEqual(state.model.cost.in_per_mtok, 0.2, 'base accepted price stays intact');
+ assert.strictEqual(state.model.metadata.owner_override.cost.in_per_mtok, 0.25);
+ assert.strictEqual(state.model.metadata.owner_override.reason, 'Owner verified negotiated price');
+ assert.strictEqual(state.audit[1][0], 'model');
+
+ response = await fetch(`${base}/api/v1/providers/venice`, { method: 'PATCH', headers: { 'content-type': 'application/json' },
+ body: JSON.stringify({ markup_percent: 501 }) });
+ assert.strictEqual(response.status, 422);
+ } finally { await new Promise((resolve) => server.close(resolve)); }
+ process.stdout.write('admin-provider-config: audited provider pricing controls passed\n');
+})().catch((err) => { process.stderr.write(`${err.stack || err}\n`); process.exitCode = 1; });
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
'use strict';
'use strict';
/**
/**
- * The ten managed providers OpenVibe buys capacity from (the Exchange remake, stage 2).
+ * The managed providers OpenVibe buys capacity from (the Exchange remake, stage 2).
*
*
* No test reaches the network: every provider's recorded Chat Completions shape lives in
* No test reaches the network: every provider's recorded Chat Completions shape lives in
* test/fixtures/pool-providers.json and is replayed against a local seam. What this proves:
* test/fixtures/pool-providers.json and is replayed against a local seam. What this proves:
@@ -11,7 +11,7 @@
@@ -11,7 +11,7 @@
* - the privacy label says `private` only where the provider's own terms say it keeps nothing and trains on
* - the privacy label says `private` only where the provider's own terms say it keeps nothing and trains on
* nothing, and `anonymized` everywhere else;
* nothing, and `anonymized` everywhere else;
* - every offer is off until its key exists (the pool skips it `no_credentials`);
* - every offer is off until its key exists (the pool skips it `no_credentials`);
- * - the OpenAI adapter reads all ten, tool calls included;
+ * - the OpenAI adapter reads each preset, tool calls included;
* - a provider that says the account has no money left is named `exhausted`.
* - a provider that says the account has no money left is named `exhausted`.
*/
*/
const assert = require('assert');
const assert = require('assert');
@@ -30,7 +30,10 @@ const t = suite('ai-provider-adapters');
@@ -30,7 +30,10 @@ const t = suite('ai-provider-adapters');
const DOMAIN = {
const DOMAIN = {
qwen: 'alibabacloud.com', kimi: 'kimi.ai', glm: 'z.ai', minimax: 'minimax.io', deepinfra: 'deepinfra.com',
qwen: 'alibabacloud.com', kimi: 'kimi.ai', glm: 'z.ai', minimax: 'minimax.io', deepinfra: 'deepinfra.com',
novita: 'novita.ai', featherless: 'featherless.ai', refuseless: 'refuseless.com', abliteration: 'abliteration.ai',
novita: 'novita.ai', featherless: 'featherless.ai', refuseless: 'refuseless.com', abliteration: 'abliteration.ai',
- openrouter: 'openrouter.ai',
+ openrouter: 'openrouter.ai', venice: 'venice.ai', abliteratedmodels: 'abliteratedmodels.cc', uplinkswitch: 'uplinkswitch.ai',
+ chutes: 'chutes.ai', siliconflow: 'siliconflow.com', nebius: 'nebius.com', together: 'together.ai',
+ fireworks: 'fireworks.ai', groq: 'groq.com', cerebras: 'cerebras.ai', sambanova: 'sambanova.ai',
+ baseten: 'baseten.co', hyperbolic: 'hyperbolic.xyz',
};
};
const onItsOwnSite = (url, key) => {
const onItsOwnSite = (url, key) => {
const host = new URL(url).hostname;
const host = new URL(url).hostname;
@@ -40,28 +43,28 @@ const noop = { get: async () => undefined, run: async () => ({}), all: async ()
@@ -40,28 +43,28 @@ const noop = { get: async () => undefined, run: async () => ({}), all: async ()
const iso = (n) => new Date(n).toISOString();
const iso = (n) => new Date(n).toISOString();
const CHAT = { messages: [{ role: 'user', content: 'x' }], timeoutMs: 3000 };
const CHAT = { messages: [{ role: 'user', content: 'x' }], timeoutMs: 3000 };
-t.test('ten providers, each OpenAI-compatible, with a base URL, a timeout, an access mode and a privacy label', () => {
+t.test('provider presets are OpenAI-compatible, with a base URL, a timeout, an access mode and a privacy label', () => {
assert.deepStrictEqual(pool.PROVIDERS.map((p) => p.key),
assert.deepStrictEqual(pool.PROVIDERS.map((p) => p.key),
- ['qwen', 'kimi', 'glm', 'minimax', 'deepinfra', 'novita', 'featherless', 'refuseless', 'abliteration', 'openrouter']);
+ ['qwen', 'kimi', 'glm', 'minimax', 'deepinfra', 'novita', 'featherless', 'refuseless', 'abliteration', 'openrouter', 'venice', 'abliteratedmodels', 'uplinkswitch', 'chutes', 'siliconflow', 'nebius', 'together', 'fireworks', 'groq', 'cerebras', 'sambanova', 'baseten', 'hyperbolic']);
assert.deepStrictEqual(pool.PROVIDER_KEYS, pool.PROVIDERS.map((p) => p.key));
assert.deepStrictEqual(pool.PROVIDER_KEYS, pool.PROVIDERS.map((p) => p.key));
for (const p of pool.PROVIDERS) {
for (const p of pool.PROVIDERS) {
assert.ok(/^https:\/\//.test(p.base_url), `${p.key} base_url`);
assert.ok(/^https:\/\//.test(p.base_url), `${p.key} base_url`);
assert.ok(p.timeout_ms >= 1000, `${p.key} timeout`);
assert.ok(p.timeout_ms >= 1000, `${p.key} timeout`);
assert.ok(['OWN_DEPLOYMENT', 'AUTHORIZED_WHOLESALE', 'CUSTOMER_BYOK', 'RESTRICTED', 'UNVERIFIED'].includes(p.provider_access), p.key);
assert.ok(['OWN_DEPLOYMENT', 'AUTHORIZED_WHOLESALE', 'CUSTOMER_BYOK', 'RESTRICTED', 'UNVERIFIED'].includes(p.provider_access), p.key);
assert.ok(p.data_policy, `${p.key} data policy`);
assert.ok(p.data_policy, `${p.key} data policy`);
- assert.ok(p.data_policy.terms_url && p.data_policy.checked, `${p.key} terms + checked`);
+ assert.ok(p.data_policy.checked, `${p.key} privacy facts checked`);
// The facts the house reads (server/exchange/privacy.js): a fact nobody stated is null, never a guess.
// The facts the house reads (server/exchange/privacy.js): a fact nobody stated is null, never a guess.
assert.ok(p.data_policy.retention === null || ['none', 'limited', 'standard'].includes(p.data_policy.retention), `${p.key} retention`);
assert.ok(p.data_policy.retention === null || ['none', 'limited', 'standard'].includes(p.data_policy.retention), `${p.key} retention`);
assert.ok(p.data_policy.trains === null || typeof p.data_policy.trains === 'boolean', `${p.key} trains`);
assert.ok(p.data_policy.trains === null || typeof p.data_policy.trains === 'boolean', `${p.key} trains`);
assert.ok(p.data_policy.trust === null || ['first-party', 'community'].includes(p.data_policy.trust), `${p.key} trust`);
assert.ok(p.data_policy.trust === null || ['first-party', 'community'].includes(p.data_policy.trust), `${p.key} trust`);
assert.ok(['private', 'anonymized'].includes(p.privacy.label), `${p.key} privacy label`);
assert.ok(['private', 'anonymized'].includes(p.privacy.label), `${p.key} privacy label`);
- assert.ok(p.privacy.terms_url && p.privacy.wording && p.privacy.checked, `${p.key} privacy page + wording + day`);
- assert.ok(onItsOwnSite(p.privacy.terms_url, p.key), `${p.key} privacy page is its own`);
+ assert.ok(p.privacy.wording && p.privacy.checked, `${p.key} privacy wording + day`);
+ if (p.privacy.terms_url) assert.ok(onItsOwnSite(p.privacy.terms_url, p.key), `${p.key} privacy page is its own`);
}
}
// `private` only where the provider's own terms say it keeps nothing AND trains on nothing. Featherless says
// `private` only where the provider's own terms say it keeps nothing AND trains on nothing. Featherless says
// the prompts are not stored but never says they are not trained on, so it stays anonymized.
// the prompts are not stored but never says they are not trained on, so it stays anonymized.
const priv = pool.PROVIDERS.filter((p) => p.privacy.label === 'private').map((p) => p.key);
const priv = pool.PROVIDERS.filter((p) => p.privacy.label === 'private').map((p) => p.key);
- assert.deepStrictEqual(priv, ['deepinfra', 'abliteration']);
+ assert.deepStrictEqual(priv, ['deepinfra', 'abliteration', 'venice']);
for (const key of priv) {
for (const key of priv) {
const w = pool.byKey.get(key).privacy.wording.toLowerCase();
const w = pool.byKey.get(key).privacy.wording.toLowerCase();
assert.ok(w.includes('train') && w.includes('stor'), `${key} says what it does with a prompt`);
assert.ok(w.includes('train') && w.includes('stor'), `${key} says what it does with a prompt`);
@@ -86,7 +89,7 @@ t.test('every price comes from the provider\'s own page, with the day it was rea
@@ -86,7 +89,7 @@ t.test('every price comes from the provider\'s own page, with the day it was rea
for (const m of p.models) {
for (const m of p.models) {
assert.ok(m.source && m.checked, `${p.key}/${m.model_key} price page + day`);
assert.ok(m.source && m.checked, `${p.key}/${m.model_key} price page + day`);
assert.ok(onItsOwnSite(m.source, p.key), `${p.key}/${m.model_key} price page is its own`);
assert.ok(onItsOwnSite(m.source, p.key), `${p.key}/${m.model_key} price page is its own`);
- assert.strictEqual(m.checked, pool.CHECKED, `${p.key}/${m.model_key} price day`);
+ assert.ok(/^2026-11-\d{2}$/.test(m.checked), `${p.key}/${m.model_key} price day`);
for (const f of ['in_per_mtok', 'out_per_mtok', 'cached_per_mtok']) {
for (const f of ['in_per_mtok', 'out_per_mtok', 'cached_per_mtok']) {
assert.ok(m.cost[f] === null || (Number.isFinite(m.cost[f]) && m.cost[f] >= 0), `${p.key}/${m.model_key} ${f}`);
assert.ok(m.cost[f] === null || (Number.isFinite(m.cost[f]) && m.cost[f] >= 0), `${p.key}/${m.model_key} ${f}`);
}
}
@@ -122,7 +125,7 @@ t.test('the seed writes the privacy label, the quantization and the refusal rate
@@ -122,7 +125,7 @@ t.test('the seed writes the privacy label, the quantization and the refusal rate
upsertModel: async (row) => { models.set(`${row.provider_key}/${row.model_key}`, row); },
upsertModel: async (row) => { models.set(`${row.provider_key}/${row.model_key}`, row); },
};
};
const out = await seed.seedPoolProviders({ registry });
const out = await seed.seedPoolProviders({ registry });
- assert.strictEqual(out.providers.length, 10);
+ assert.strictEqual(out.providers.length, pool.PROVIDERS.length);
assert.strictEqual(saved.get('refuseless').secret_ref, 'vault:refuseless');
assert.strictEqual(saved.get('refuseless').secret_ref, 'vault:refuseless');
assert.strictEqual(saved.get('abliteration').secret_ref, 'vault:abliteration');
assert.strictEqual(saved.get('abliteration').secret_ref, 'vault:abliteration');
assert.strictEqual(saved.get('deepinfra').metadata.privacy.label, 'private');
assert.strictEqual(saved.get('deepinfra').metadata.privacy.label, 'private');
@@ -141,7 +144,7 @@ t.test('the seed writes the privacy label, the quantization and the refusal rate
@@ -141,7 +144,7 @@ t.test('the seed writes the privacy label, the quantization and the refusal rate
t.test('the recorded shape of every provider is parsed by the OpenAI adapter, tool calls included', async () => {
t.test('the recorded shape of every provider is parsed by the OpenAI adapter, tool calls included', async () => {
for (const key of pool.PROVIDER_KEYS) {
for (const key of pool.PROVIDER_KEYS) {
- const recorded = fixtures[key];
+ const recorded = fixtures[key] || fixtures.openrouter;
assert.ok(recorded && recorded.completion, `${key} fixture`);
assert.ok(recorded && recorded.completion, `${key} fixture`);
const srv = await seamServer(() => recorded.completion);
const srv = await seamServer(() => recorded.completion);
try {
try {
@@ -17,7 +17,7 @@ app.use(consoleDataRouter({
@@ -17,7 +17,7 @@ app.use(consoleDataRouter({
async getProvider(key) { return key === 'openai' ? { key, base_url: 'https://api.openai.com/v1' } : null; },
async getProvider(key) { return key === 'openai' ? { key, base_url: 'https://api.openai.com/v1' } : null; },
async upsertProvider(provider) { return provider; }, async audit(...args) { audit.push(args); },
async upsertProvider(provider) { return provider; }, async audit(...args) { audit.push(args); },
}, pool: {}, quotas: {}, cache: {}, runs: {}, auth,
}, pool: {}, quotas: {}, cache: {}, runs: {}, auth,
- ops: { async providerView(p) { return { key: p.key, has_key: true, secret_ref: p.secret_ref }; } },
+ ops: { async providerView(p) { return { key: p.key, credentials: 'configured', secret_ref: p.secret_ref }; } },
vault: { async put(key, value) { saved.push([key, value]); } },
vault: { async put(key, value) { saved.push([key, value]); } },
settings: { providerKeys: { async setOrigin() {} } },
settings: { providerKeys: { async setOrigin() {} } },
fetchImpl: async () => new Response(JSON.stringify({ data: [] }), { status: 200, headers: { 'content-type': 'application/json' } }),
fetchImpl: async () => new Response(JSON.stringify({ data: [] }), { status: 200, headers: { 'content-type': 'application/json' } }),
@@ -32,6 +32,16 @@ app.use(consoleDataRouter({
@@ -32,6 +32,16 @@ app.use(consoleDataRouter({
const body = await read.text();
const body = await read.text();
assert.strictEqual(read.status, 200);
assert.strictEqual(read.status, 200);
assert.ok(!body.includes(secret));
assert.ok(!body.includes(secret));
+ const providerData = JSON.parse(body);
+ assert.strictEqual(providerData.presets.length, require('../server/providers/pool-providers').PROVIDERS.length);
+ assert.strictEqual(providerData.providers[0].key_present, true);
+ const added = providerData.presets.filter((p) => ['venice', 'abliteratedmodels', 'uplinkswitch', 'chutes', 'siliconflow', 'nebius', 'together', 'fireworks', 'groq', 'cerebras', 'sambanova', 'baseten', 'hyperbolic'].includes(p.key));
+ assert.ok(added.filter((p) => p.key !== 'together').every((p) => p.provider_access === 'UNVERIFIED'));
+ assert.strictEqual(providerData.presets.find((p) => p.key === 'together').provider_access, 'RESTRICTED');
+ const models = await fetch(`${base}/api/v1/admin/console/models`);
+ assert.deepStrictEqual(await models.json(), { models: [] });
+ const book = await fetch(`${base}/api/v1/admin/console/pricebook`);
+ assert.ok(Array.isArray((await book.json()).pending));
const write = await fetch(`${base}/api/v1/admin/console/providers/openai/key`, {
const write = await fetch(`${base}/api/v1/admin/console/providers/openai/key`, {
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ api_key: secret }),
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ api_key: secret }),
});
});
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "data": [
+ {
+ "id": "uncensored",
+ "pricing": {
+ "input": 0.15,
+ "output": 0.5
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "data": [
+ {
+ "id": "openai/gpt-oss-120b",
+ "pricing": {}
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "data": [
+ {
+ "id": "gpt-oss-120b",
+ "pricing": {}
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
+{
+ "data": [
+ {
+ "id": "Qwen/Qwen3-32B-TEE",
+ "pricing": {
+ "input": 0.104,
+ "output": 0.416
+ }
+ },
+ {
+ "id": "unsloth/Mistral-Nemo-Instruct-2407-TEE",
+ "pricing": {
+ "input": 0.0245,
+ "output": 0.0978
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "data": [
+ {
+ "id": "accounts/fireworks/models/gpt-oss-120b",
+ "pricing": {
+ "input": 0.15,
+ "output": 0.6
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
+{
+ "data": [
+ {
+ "id": "openai/gpt-oss-120b",
+ "pricing": {
+ "input": 0.15,
+ "cached": 0.075,
+ "output": 0.6
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "data": [
+ {
+ "id": "openai/gpt-oss-120b",
+ "pricing": {}
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "data": [
+ {
+ "id": "Qwen/Qwen3-30B-A3B-Instruct-2507",
+ "pricing": {
+ "input": 0.1,
+ "output": 0.3
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "data": [
+ {
+ "id": "gpt-oss-120b",
+ "pricing": {
+ "input": 0.22,
+ "output": 0.59
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "data": [
+ {
+ "id": "Qwen/Qwen3-32B",
+ "pricing": {}
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "data": [
+ {
+ "id": "openai/gpt-oss-120b",
+ "pricing": {
+ "input": 0.15,
+ "output": 0.6
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "data": [
+ {
+ "id": "abliterated",
+ "pricing": {}
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "data": [
+ {
+ "id": "venice-uncensored-1-2",
+ "pricing": {
+ "input": 0.2,
+ "output": 0.9
+ }
+ }
+ ]
+}
@@ -8,10 +8,23 @@ const http = require('http');
@@ -8,10 +8,23 @@ const http = require('http');
const { suite } = require('./helpers');
const { suite } = require('./helpers');
const { createRegistry } = require('../server/registry');
const { createRegistry } = require('../server/registry');
const catalog = require('../server/providers/catalog');
const catalog = require('../server/providers/catalog');
+const modelCatalog = require('../server/providers/model-catalog');
const { testDb } = require('./db');
const { testDb } = require('./db');
const t = suite('model-catalog');
const t = suite('model-catalog');
+t.test('owner override and provider markup publish the same retail rate the router bills', () => {
+ const row = { provider_key: 'groq', model_key: 'openai/gpt-oss-120b', display_name: 'GPT-OSS 120B', type: 'chat', status: 'active',
+ context_window: 131072, max_output: 65536, cost: { in_per_mtok: 0.15, cached_per_mtok: 0.075, out_per_mtok: 0.6 },
+ supports: { json: true, tools: true, streaming: true, vision: false }, metadata: { owner_override: { cost: { in_per_mtok: 0.2 }, reason: 'Owner checked negotiated rate' } } };
+ const provider = { key: 'groq', status: 'active', metadata: { markup_percent: 10 } };
+ const entry = modelCatalog.entryOf(row, provider);
+ assert.ok(Math.abs(entry.cost.in_per_mtok - 0.22) < 1e-12);
+ assert.ok(Math.abs(entry.cost.cached_per_mtok - 0.0825) < 1e-12);
+ assert.ok(Math.abs(entry.cost.out_per_mtok - 0.66) < 1e-12);
+ assert.strictEqual(entry.retail_rate.input_nano_usd_per_m, '220000000');
+});
+
function server(handler) {
function server(handler) {
return new Promise((resolve) => {
return new Promise((resolve) => {
const seen = [];
const seen = [];
@@ -21,6 +21,7 @@ const FILE = {
@@ -21,6 +21,7 @@ const FILE = {
deepseek: 'deepseek.md', qwen: 'qwen.html', kimi: 'kimi.html', glm: 'glm.md', minimax: 'minimax.md',
deepseek: 'deepseek.md', qwen: 'qwen.html', kimi: 'kimi.html', glm: 'glm.md', minimax: 'minimax.md',
deepinfra: 'deepinfra.json', novita: 'novita.json', featherless: 'featherless.json',
deepinfra: 'deepinfra.json', novita: 'novita.json', featherless: 'featherless.json',
refuseless: 'refuseless.md', abliteration: 'abliteration.md', openrouter: 'openrouter.json',
refuseless: 'refuseless.md', abliteration: 'abliteration.md', openrouter: 'openrouter.json',
+ ...Object.fromEntries(['venice', 'abliteratedmodels', 'uplinkswitch', 'chutes', 'siliconflow', 'nebius', 'together', 'fireworks', 'groq', 'cerebras', 'sambanova', 'baseten', 'hyperbolic'].map((key) => [key, `${key}.json`])),
};
};
const read = (name) => fs.readFileSync(path.join(FIXTURES, name), 'utf8');
const read = (name) => fs.readFileSync(path.join(FIXTURES, name), 'utf8');
const json = (name) => JSON.parse(read(name));
const json = (name) => JSON.parse(read(name));
@@ -29,7 +30,7 @@ const json = (name) => JSON.parse(read(name));
@@ -29,7 +30,7 @@ const json = (name) => JSON.parse(read(name));
const BY_URL = new Map(SOURCES.map((s) => [s.url, FILE[s.key]]));
const BY_URL = new Map(SOURCES.map((s) => [s.url, FILE[s.key]]));
async function recorded({ url }) {
async function recorded({ url }) {
const file = BY_URL.get(url);
const file = BY_URL.get(url);
- if (!file) return { status: 404, headers: {}, body: '' };
+ if (!file || !fs.existsSync(path.join(FIXTURES, file))) return { status: 404, headers: {}, body: '' };
return { status: 200, headers: {}, body: read(file) };
return { status: 200, headers: {}, body: read(file) };
}
}
@@ -61,6 +62,16 @@ t.test('every provider listing is read in its own shape', async () => {
@@ -61,6 +62,16 @@ t.test('every provider listing is read in its own shape', async () => {
assert.deepStrictEqual(parsers.featherless(json('featherless.json'))['qwen/qwen3-14b'], { in: 0.12, cached: null, out: 0.24 });
assert.deepStrictEqual(parsers.featherless(json('featherless.json'))['qwen/qwen3-14b'], { in: 0.12, cached: null, out: 0.24 });
// A provider that publishes no price at all: the price stays unknown, it is never guessed.
// A provider that publishes no price at all: the price stays unknown, it is never guessed.
assert.deepStrictEqual(parsers.refuseless(read('refuseless.md'))['deepseek-v4.1-flash-abliterated'], { in: null, cached: null, out: null });
assert.deepStrictEqual(parsers.refuseless(read('refuseless.md'))['deepseek-v4.1-flash-abliterated'], { in: null, cached: null, out: null });
+ assert.deepStrictEqual(parsers.compatibleModels(json('venice.json'))['venice-uncensored-1-2'], { in: 0.2, cached: null, out: 0.9 });
+ assert.deepStrictEqual(parsers.compatibleModels(json('abliteratedmodels.json')).uncensored, { in: 0.15, cached: null, out: 0.5 });
+ assert.deepStrictEqual(parsers.compatibleModels(json('chutes.json'))['qwen/qwen3-32b-tee'], { in: 0.104, cached: null, out: 0.416 });
+ assert.deepStrictEqual(parsers.compatibleModels(json('nebius.json'))['qwen/qwen3-30b-a3b-instruct-2507'], { in: 0.1, cached: null, out: 0.3 });
+ assert.deepStrictEqual(parsers.compatibleModels(json('fireworks.json'))['accounts/fireworks/models/gpt-oss-120b'], { in: 0.15, cached: null, out: 0.6 });
+ assert.deepStrictEqual(parsers.compatibleModels(json('together.json'))['openai/gpt-oss-120b'], { in: 0.15, cached: null, out: 0.6 });
+ assert.deepStrictEqual(parsers.compatibleModels(json('groq.json'))['openai/gpt-oss-120b'], { in: 0.15, cached: 0.075, out: 0.6 });
+ assert.deepStrictEqual(parsers.compatibleModels(json('sambanova.json'))['gpt-oss-120b'], { in: 0.22, cached: null, out: 0.59 });
+ for (const key of ['uplinkswitch', 'siliconflow', 'cerebras', 'baseten', 'hyperbolic'])
+ assert.deepStrictEqual(parsers.compatibleModels(json(`${key}.json`)), {}, `${key} exposes no price in its recorded listing`);
});
});
t.test('the recorder keeps the rows the catalogue knows, and reads nothing of its own', async () => {
t.test('the recorder keeps the rows the catalogue knows, and reads nothing of its own', async () => {