openvibers/openvibe.ai Commits

Add open-weight provider presets and owner pricing controls (#408)

OP OpenVibers committed 4 days ago · 026da87d6e · 27 files, +421 −29

Changes

ddiff --git a/docs/provider-matrix.md b/docs/provider-matrix.md
iindex 40e32da..c459d18 100644
---- a/docs/provider-matrix.md
++++ b/docs/provider-matrix.md
@@@ -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 |
++
## 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.
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`.
ddiff --git a/server/api/admin.js b/server/api/admin.js
iindex b5a52c5..63beb5a 100644
---- a/server/api/admin.js
++++ b/server/api/admin.js
@@@ -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');
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, aut
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) => {
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
ddiff --git a/server/api/console-data.js b/server/api/console-data.js
iindex 9f4eccb..f5eb634 100644
---- a/server/api/console-data.js
++++ b/server/api/console-data.js
@@@ -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);
ddiff --git a/server/pricebook/parsers.js b/server/pricebook/parsers.js
iindex 7059fd5..7e80b90 100644
---- a/server/pricebook/parsers.js
++++ b/server/pricebook/parsers.js
@@@ -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,
};
ddiff --git a/server/pricebook/sources.js b/server/pricebook/sources.js
iindex 5c4d6bd..8259227 100644
---- a/server/pricebook/sources.js
++++ b/server/pricebook/sources.js
@@@ -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). */
ddiff --git a/server/providers/index.js b/server/providers/index.js
iindex fe3cd6f..3a9ddd9 100644
---- a/server/providers/index.js
++++ b/server/providers/index.js
@@@ -325,7 +325,13 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no
const 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);
ddiff --git a/server/providers/model-catalog.js b/server/providers/model-catalog.js
iindex cbe749b..e746d8b 100644
---- a/server/providers/model-catalog.js
++++ b/server/providers/model-catalog.js
@@@ -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;
}
ddiff --git a/server/providers/pool-providers.js b/server/providers/pool-providers.js
iindex e8b025c..9128c69 100644
---- a/server/providers/pool-providers.js
++++ b/server/providers/pool-providers.js
@@@ -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]));
ddiff --git a/server/registry.js b/server/registry.js
iindex ded4af1..31da00f 100644
---- a/server/registry.js
++++ b/server/registry.js
@@@ -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,
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.e
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,
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);
}
ddiff --git a/test/admin-provider-config.test.js b/test/admin-provider-config.test.js
nnew file mode 100644
iindex 0000000..4afece5
---- /dev/null
++++ b/test/admin-provider-config.test.js
@@@ -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; });
ddiff --git a/test/ai-provider-adapters.test.js b/test/ai-provider-adapters.test.js
iindex ffe95b5..6853e07 100644
---- a/test/ai-provider-adapters.test.js
++++ b/test/ai-provider-adapters.test.js
@@@ -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 rea
for (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 rate
upsertModel: 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 rate
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) {
-- const recorded = fixtures[key];
++ const recorded = fixtures[key] || fixtures.openrouter;
assert.ok(recorded && recorded.completion, `${key} fixture`);
const srv = await seamServer(() => recorded.completion);
try {
ddiff --git a/test/console-data.test.js b/test/console-data.test.js
iindex b9a9b1a..113a406 100644
---- a/test/console-data.test.js
++++ b/test/console-data.test.js
@@@ -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 }),
});
ddiff --git a/test/fixtures/pricebook/abliteratedmodels.json b/test/fixtures/pricebook/abliteratedmodels.json
nnew file mode 100644
iindex 0000000..91810db
---- /dev/null
++++ b/test/fixtures/pricebook/abliteratedmodels.json
@@@ -0,0 +1,11 @@
++{
++ "data": [
++ {
++ "id": "uncensored",
++ "pricing": {
++ "input": 0.15,
++ "output": 0.5
++ }
++ }
++ ]
++}
ddiff --git a/test/fixtures/pricebook/baseten.json b/test/fixtures/pricebook/baseten.json
nnew file mode 100644
iindex 0000000..5ccb6fc
---- /dev/null
++++ b/test/fixtures/pricebook/baseten.json
@@@ -0,0 +1,8 @@
++{
++ "data": [
++ {
++ "id": "openai/gpt-oss-120b",
++ "pricing": {}
++ }
++ ]
++}
ddiff --git a/test/fixtures/pricebook/cerebras.json b/test/fixtures/pricebook/cerebras.json
nnew file mode 100644
iindex 0000000..46889dc
---- /dev/null
++++ b/test/fixtures/pricebook/cerebras.json
@@@ -0,0 +1,8 @@
++{
++ "data": [
++ {
++ "id": "gpt-oss-120b",
++ "pricing": {}
++ }
++ ]
++}
ddiff --git a/test/fixtures/pricebook/chutes.json b/test/fixtures/pricebook/chutes.json
nnew file mode 100644
iindex 0000000..6f2cab7
---- /dev/null
++++ b/test/fixtures/pricebook/chutes.json
@@@ -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
++ }
++ }
++ ]
++}
ddiff --git a/test/fixtures/pricebook/fireworks.json b/test/fixtures/pricebook/fireworks.json
nnew file mode 100644
iindex 0000000..868b032
---- /dev/null
++++ b/test/fixtures/pricebook/fireworks.json
@@@ -0,0 +1,11 @@
++{
++ "data": [
++ {
++ "id": "accounts/fireworks/models/gpt-oss-120b",
++ "pricing": {
++ "input": 0.15,
++ "output": 0.6
++ }
++ }
++ ]
++}
ddiff --git a/test/fixtures/pricebook/groq.json b/test/fixtures/pricebook/groq.json
nnew file mode 100644
iindex 0000000..0ab7a7d
---- /dev/null
++++ b/test/fixtures/pricebook/groq.json
@@@ -0,0 +1,12 @@
++{
++ "data": [
++ {
++ "id": "openai/gpt-oss-120b",
++ "pricing": {
++ "input": 0.15,
++ "cached": 0.075,
++ "output": 0.6
++ }
++ }
++ ]
++}
ddiff --git a/test/fixtures/pricebook/hyperbolic.json b/test/fixtures/pricebook/hyperbolic.json
nnew file mode 100644
iindex 0000000..5ccb6fc
---- /dev/null
++++ b/test/fixtures/pricebook/hyperbolic.json
@@@ -0,0 +1,8 @@
++{
++ "data": [
++ {
++ "id": "openai/gpt-oss-120b",
++ "pricing": {}
++ }
++ ]
++}
ddiff --git a/test/fixtures/pricebook/nebius.json b/test/fixtures/pricebook/nebius.json
nnew file mode 100644
iindex 0000000..d59d650
---- /dev/null
++++ b/test/fixtures/pricebook/nebius.json
@@@ -0,0 +1,11 @@
++{
++ "data": [
++ {
++ "id": "Qwen/Qwen3-30B-A3B-Instruct-2507",
++ "pricing": {
++ "input": 0.1,
++ "output": 0.3
++ }
++ }
++ ]
++}
ddiff --git a/test/fixtures/pricebook/sambanova.json b/test/fixtures/pricebook/sambanova.json
nnew file mode 100644
iindex 0000000..932751e
---- /dev/null
++++ b/test/fixtures/pricebook/sambanova.json
@@@ -0,0 +1,11 @@
++{
++ "data": [
++ {
++ "id": "gpt-oss-120b",
++ "pricing": {
++ "input": 0.22,
++ "output": 0.59
++ }
++ }
++ ]
++}
ddiff --git a/test/fixtures/pricebook/siliconflow.json b/test/fixtures/pricebook/siliconflow.json
nnew file mode 100644
iindex 0000000..a7e5d33
---- /dev/null
++++ b/test/fixtures/pricebook/siliconflow.json
@@@ -0,0 +1,8 @@
++{
++ "data": [
++ {
++ "id": "Qwen/Qwen3-32B",
++ "pricing": {}
++ }
++ ]
++}
ddiff --git a/test/fixtures/pricebook/together.json b/test/fixtures/pricebook/together.json
nnew file mode 100644
iindex 0000000..1352db5
---- /dev/null
++++ b/test/fixtures/pricebook/together.json
@@@ -0,0 +1,11 @@
++{
++ "data": [
++ {
++ "id": "openai/gpt-oss-120b",
++ "pricing": {
++ "input": 0.15,
++ "output": 0.6
++ }
++ }
++ ]
++}
ddiff --git a/test/fixtures/pricebook/uplinkswitch.json b/test/fixtures/pricebook/uplinkswitch.json
nnew file mode 100644
iindex 0000000..0b02259
---- /dev/null
++++ b/test/fixtures/pricebook/uplinkswitch.json
@@@ -0,0 +1,8 @@
++{
++ "data": [
++ {
++ "id": "abliterated",
++ "pricing": {}
++ }
++ ]
++}
ddiff --git a/test/fixtures/pricebook/venice.json b/test/fixtures/pricebook/venice.json
nnew file mode 100644
iindex 0000000..b370f16
---- /dev/null
++++ b/test/fixtures/pricebook/venice.json
@@@ -0,0 +1,11 @@
++{
++ "data": [
++ {
++ "id": "venice-uncensored-1-2",
++ "pricing": {
++ "input": 0.2,
++ "output": 0.9
++ }
++ }
++ ]
++}
ddiff --git a/test/model-catalog.test.js b/test/model-catalog.test.js
iindex 202ee70..9b0eb71 100644
---- a/test/model-catalog.test.js
++++ b/test/model-catalog.test.js
@@@ -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 = [];
ddiff --git a/test/price-book.test.js b/test/price-book.test.js
iindex b5d3086..d005341 100644
---- a/test/price-book.test.js
++++ b/test/price-book.test.js
@@@ -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 () => {