openvibers/openvibe.ai Commits

Add eleven cheap hosted inference providers (disabled) (#444)

OP OpenVibers committed 3 days ago · 81fefe1bd6 · 6 files, +263 −2

* Add eleven cheap hosted inference providers (disabled)

Each row is seeded disabled with a vault:<key> slot and no key, an https OpenAI-compatible base URL on its own documented host, and protocol metadata, so it joins model sync, price-book reads, circuits, failover and capability membership once the owner adds a key. Price-book reads the keyless per-token lists of Inference.net, AtlasCloud, Mancer and Friendli. Skipped per docs and live probes: kluster.ai (sunset), Lambda hosted inference (winding down), Targon (410), GMI/nCompass/Crusoe (no docs-confirmed base today), OVHcloud (per-model regional URLs).

* Drop Mancer and price-book sources that would misread prices; run the new suite

Mancer's model list carries credit-unit prices whose dollar value is unverified; the catalogue sync would import them as per-token dollars, so Mancer stays out until the unit is verified. The price book reads per-1M figures only, so the per-token lists of Inference.net, AtlasCloud and Friendli stay out of the price book (the catalogue sync still imports their per-token prices). The new test file now ends with t.run() like every other suite.

Changes

ddiff --git a/docs/provider-matrix.json b/docs/provider-matrix.json
iindex 32b8880..6596db4 100644
---- a/docs/provider-matrix.json
++++ b/docs/provider-matrix.json
@@@ -314,6 +314,116 @@
"terms_url": "Various OS licenses",
"terms_effective": null,
"checked": "2026-11-03"
++ },
++ {
++ "provider": "Parasail",
++ "api_format": "OpenAI-compatible",
++ "existing_adapter": "openai",
++ "byok": null,
++ "access_mode_default": "UNVERIFIED",
++ "resale_terms_quote": "No resale clause verified; terms silent on retention and training.",
++ "terms_url": "https://docs.parasail.io/parasail-docs/products/overview/models",
++ "terms_effective": null,
++ "checked": "2026-11-10"
++ },
++ {
++ "provider": "Particle AI (particle.ai)",
++ "api_format": "OpenAI-compatible",
++ "existing_adapter": "openai",
++ "byok": null,
++ "access_mode_default": "UNVERIFIED",
++ "resale_terms_quote": "No resale clause verified; terms silent on retention and training.",
++ "terms_url": "https://docs.particle.ai/",
++ "terms_effective": null,
++ "checked": "2026-11-10"
++ },
++ {
++ "provider": "Inference.net",
++ "api_format": "OpenAI-compatible",
++ "existing_adapter": "openai",
++ "byok": null,
++ "access_mode_default": "UNVERIFIED",
++ "resale_terms_quote": "No resale clause verified; terms silent on retention and training.",
++ "terms_url": "https://docs.inference.net/",
++ "terms_effective": null,
++ "checked": "2026-11-10"
++ },
++ {
++ "provider": "AtlasCloud",
++ "api_format": "OpenAI-compatible",
++ "existing_adapter": "openai",
++ "byok": null,
++ "access_mode_default": "UNVERIFIED",
++ "resale_terms_quote": "No resale clause verified; terms silent on retention and training.",
++ "terms_url": "https://www.atlascloud.ai/docs",
++ "terms_effective": null,
++ "checked": "2026-11-10"
++ },
++ {
++ "provider": "Avian (avian.io)",
++ "api_format": "OpenAI-compatible",
++ "existing_adapter": "openai",
++ "byok": null,
++ "access_mode_default": "UNVERIFIED",
++ "resale_terms_quote": "No resale clause verified; terms silent on retention and training.",
++ "terms_url": "https://avian.io/docs/",
++ "terms_effective": null,
++ "checked": "2026-11-10"
++ },
++ {
++ "provider": "NanoGPT",
++ "api_format": "OpenAI-compatible",
++ "existing_adapter": "openai",
++ "byok": null,
++ "access_mode_default": "UNVERIFIED",
++ "resale_terms_quote": "PAYG terms explicitly allow reselling access to the Services as part of your own service offerings; subscription use prohibits commercial/resale use. No executed wholesale agreement, so still UNVERIFIED.",
++ "terms_url": "https://nano-gpt.com/pricing",
++ "terms_effective": null,
++ "checked": "2026-11-10"
++ },
++ {
++ "provider": "Infermatic",
++ "api_format": "OpenAI-compatible",
++ "existing_adapter": "openai",
++ "byok": null,
++ "access_mode_default": "UNVERIFIED",
++ "resale_terms_quote": "No resale clause verified; terms silent on retention and training.",
++ "terms_url": "https://ui.infermatic.ai/docs",
++ "terms_effective": null,
++ "checked": "2026-11-10"
++ },
++ {
++ "provider": "ArliAI",
++ "api_format": "OpenAI-compatible",
++ "existing_adapter": "openai",
++ "byok": null,
++ "access_mode_default": "UNVERIFIED",
++ "resale_terms_quote": "No resale clause verified; terms silent on retention and training.",
++ "terms_url": "https://www.arliai.com/",
++ "terms_effective": null,
++ "checked": "2026-11-10"
++ },
++ {
++ "provider": "Friendli",
++ "api_format": "OpenAI-compatible",
++ "existing_adapter": "openai",
++ "byok": null,
++ "access_mode_default": "UNVERIFIED",
++ "resale_terms_quote": "No resale clause verified; terms silent on retention and training.",
++ "terms_url": "https://friendli.ai/docs/guides/model-apis/introduction",
++ "terms_effective": null,
++ "checked": "2026-11-10"
++ },
++ {
++ "provider": "Scaleway Generative APIs",
++ "api_format": "OpenAI-compatible",
++ "existing_adapter": "openai",
++ "byok": null,
++ "access_mode_default": "UNVERIFIED",
++ "resale_terms_quote": "No resale clause verified; terms silent on retention and training.",
++ "terms_url": "https://www.scaleway.com/en/developers/api/generative-apis",
++ "terms_effective": null,
++ "checked": "2026-11-10"
}
]
}
ddiff --git a/docs/provider-matrix.md b/docs/provider-matrix.md
iindex c459d18..3c79426 100644
---- a/docs/provider-matrix.md
++++ b/docs/provider-matrix.md
@@@ -65,6 +65,23 @@ These presets are seeded disabled in practice until the owner adds a provider ke
| 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) |
++## Cheap hosted inference added 2026-11-10
++
++Endpoints, keyless model lists and key requirements were probed on 2026-11-10 (no account, no key, no paid call). Retention, training and resale terms are unstated for all ten; NanoGPT's terms alone explicitly allow reselling PAYG access, which still keeps its default `UNVERIFIED` until an executed agreement exists. Prices are not read by the price book for these ten (it takes per-1M figures; the catalogue sync takes the per-token strings Inference.net, AtlasCloud and Friendli publish). Avian publishes per-million figures the existing sync cannot read, so its prices stay on the owner's panel.
++
++| Provider | Base URL | Privacy facts confirmed | Access default | Own provider listing / terms |
++|---|---|---|---|---|
++| Parasail | `https://api.parasail.io/v1` | Retention/training unstated; model list needs a key | `UNVERIFIED` | [Model docs](https://docs.parasail.io/parasail-docs/products/overview/models) |
++| Particle AI | `https://api.particle.ai/v1` | Retention/training unstated; model list needs a key | `UNVERIFIED` | [Docs](https://docs.particle.ai/) |
++| Inference.net | `https://api.inference.net/v1` | Retention/training unstated; keyless model list with per-token prices | `UNVERIFIED` | [Docs](https://docs.inference.net/) |
++| AtlasCloud | `https://api.atlascloud.ai/v1` | Retention/training unstated; keyless model list with per-token prices | `UNVERIFIED` | [Docs](https://www.atlascloud.ai/docs) |
++| Avian | `https://api.avian.io/v1` | Retention/training unstated; keyless model list, per-million prices not readable by sync | `UNVERIFIED` | [Docs](https://avian.io/docs/) |
++| NanoGPT | `https://api.nano-gpt.com/v1` | Terms allow reselling PAYG access (no markup); keyless model list carries no prices | `UNVERIFIED` | [Pricing](https://nano-gpt.com/pricing) |
++| Infermatic | `https://api.totalgpt.ai/v1` | Retention/training unstated; model list needs a key | `UNVERIFIED` | [Docs](https://ui.infermatic.ai/docs) |
++| ArliAI | `https://api.arliai.com/v1` | Retention/training unstated; keyless custom model list at `/model/all` | `UNVERIFIED` | [Site](https://www.arliai.com/) |
++| Friendli | `https://api.friendli.ai/serverless/v1` | Retention/training unstated; keyless model list with per-token prices | `UNVERIFIED` | [Model APIs intro](https://friendli.ai/docs/guides/model-apis/introduction) |
++| Scaleway | `https://api.scaleway.ai/v1` | Retention/training unstated; model list needs a key | `UNVERIFIED` | [Generative APIs](https://www.scaleway.com/en/developers/api/generative-apis) |
++
## 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.
ddiff --git a/docs/provider-registry.md b/docs/provider-registry.md
iindex e081f94..bb6a6ee 100644
---- a/docs/provider-registry.md
++++ b/docs/provider-registry.md
@@@ -48,9 +48,21 @@ Required-provider documentation was checked on 2026-11-10. Meta now documents Mo
| SearXNG (searxng) | adapter / http | Owner-configured | Not applicable | [Source](https://docs.searxng.org/dev/search_api.html) |
| Openverse (openverse) | adapter / http | https://api.openverse.org/v1 | Not applicable | [Source](https://api.openverse.org/) |
| Wikimedia Commons (commons) | adapter / http | https://commons.wikimedia.org/w/api.php | Not applicable | [Source](https://www.mediawiki.org/wiki/API:Search) |
++| Parasail (parasail) | openai / openai | https://api.parasail.io/v1 | https://api.parasail.io/v1/models (key required) | [Source](https://docs.parasail.io/parasail-docs/products/overview/models) |
++| Particle AI (particle-ai) | openai / openai | https://api.particle.ai/v1 | https://api.particle.ai/v1/models (key required) | [Source](https://docs.particle.ai/) |
++| Inference.net (inference-net) | openai / openai | https://api.inference.net/v1 | https://api.inference.net/v1/models (keyless) | [Source](https://docs.inference.net/) |
++| AtlasCloud (atlas-cloud) | openai / openai | https://api.atlascloud.ai/v1 | https://api.atlascloud.ai/v1/models (keyless) | [Source](https://www.atlascloud.ai/docs) |
++| Avian (avian) | openai / openai | https://api.avian.io/v1 | https://api.avian.io/v1/models (keyless) | [Source](https://avian.io/docs/) |
++| NanoGPT (nanogpt) | openai / openai | https://api.nano-gpt.com/v1 | https://api.nano-gpt.com/v1/models (keyless, no prices) | [Source](https://nano-gpt.com/pricing) |
++| Infermatic (infermatic) | openai / openai | https://api.totalgpt.ai/v1 | https://api.totalgpt.ai/v1/models (key required) | [Source](https://ui.infermatic.ai/docs) |
++| ArliAI (arliai) | openai / openai | https://api.arliai.com/v1 | https://api.arliai.com/model/all (keyless) | [Source](https://www.arliai.com/) |
++| Friendli (friendli) | openai / openai | https://api.friendli.ai/serverless/v1 | https://api.friendli.ai/serverless/v1/models (keyless) | [Source](https://friendli.ai/docs/guides/model-apis/introduction) |
++| Scaleway (scaleway) | openai / openai | https://api.scaleway.ai/v1 | https://api.scaleway.ai/v1/models (key required) | [Source](https://www.scaleway.com/en/developers/api/generative-apis) |
Search capabilities use web-search and image-search, matching the shared placement capability syntax. Brave web and images share one provider/key, as do Serper web and images. Openverse and Wikimedia Commons require no key. SearXNG has no invented public address; the owner supplies their self-hosted base.
++Cheap hosted inference (2026-11-10, owner request): the ten providers added at the end of the table are seeded disabled with a `vault:<key>` slot and no key, exactly like the existing pool rows, so they take part in model sync, circuits, failover order and capability memberships as soon as the owner puts a key in the console and enables them. The catalogue sync (`server/providers/catalog.js`) reads the per-token prices each model list carries and imports them into new models (Inference.net, AtlasCloud and Friendli carry per-token strings; the price book only reads per-1M figures, so it stays out of these lists). Parasail, Particle AI, Infermatic and Scaleway need a key for their model lists, so sync runs after the owner adds one; NanoGPT's list carries no prices, and Avian publishes per-million figures the existing parsers cannot read — both stay on the owner's price panel. Skipped, not guessed: kluster.ai (services sunset 2026-06-09), Lambda's hosted inference (winding down; `api.lambda.com` does not answer), Targon (`api.targon.com/v1/models` answers 410 Gone), GMI Cloud, nCompass and Crusoe (no docs-confirmed plain OpenAI chat base URL today), Mancer (its model list carries credit-unit prices whose dollar value nobody has verified; sync would import them as per-token dollars), and OVHcloud AI Endpoints (per-model regional URLs under `endpoints.kepler.ai.cloud.ovh.net`, no single base URL). Two research notes disagree with the live docs and the docs win: Infermatic's base is `api.totalgpt.ai/v1`, not `ui.infermatic.ai/v1` (prices.json), and Friendli's serverless base `api.friendli.ai/serverless/v1` is published in its docs today.
++
For Fable: the existing providers console now receives all seeded provider rows and protocol metadata. Its visual grouping and capability presentation remain for the main session.
Owner key controls use the existing encrypted `ai.provider_keys` store. The console JSON exposes only presence,
ddiff --git a/server/providers/pool-providers.js b/server/providers/pool-providers.js
iindex 03cfa8e..7e457a5 100644
---- a/server/providers/pool-providers.js
++++ b/server/providers/pool-providers.js
@@@ -716,6 +716,30 @@ const PROVIDERS = [
{ key: 'commons', display_name: 'Wikimedia Commons', kind: 'http', base_url: 'https://commons.wikimedia.org/w/api.php', auth_mode: 'none', models_endpoint: null,
capabilities: ['image-search'], search_adapters: ['commons'], docs_url: 'https://www.mediawiki.org/wiki/API:Search' },
++ // Cheap hosted inference (owner, 2026-11-10): every endpoint below was read and probed keyless today.
++ // Each starts disabled with no key; the owner puts its key in the owner settings and syncs its model list.
++ { key: 'parasail', display_name: 'Parasail', base_url: 'https://api.parasail.io/v1',
++ docs_url: 'https://docs.parasail.io/parasail-docs/products/overview/models', capabilities: ['chat', 'embed'] },
++ { key: 'particle-ai', display_name: 'Particle AI', base_url: 'https://api.particle.ai/v1',
++ docs_url: 'https://docs.particle.ai/', capabilities: ['chat'] },
++ { key: 'inference-net', display_name: 'Inference.net', base_url: 'https://api.inference.net/v1', models_public: true,
++ docs_url: 'https://docs.inference.net/', capabilities: ['chat'] },
++ { key: 'atlas-cloud', display_name: 'AtlasCloud', base_url: 'https://api.atlascloud.ai/v1', models_public: true,
++ docs_url: 'https://www.atlascloud.ai/docs', capabilities: ['chat'] },
++ { key: 'avian', display_name: 'Avian', base_url: 'https://api.avian.io/v1', models_public: true,
++ docs_url: 'https://avian.io/docs/', capabilities: ['chat'] },
++ { key: 'nanogpt', display_name: 'NanoGPT', base_url: 'https://api.nano-gpt.com/v1', models_public: true,
++ docs_url: 'https://nano-gpt.com/pricing', capabilities: ['chat'] },
++ { key: 'infermatic', display_name: 'Infermatic', base_url: 'https://api.totalgpt.ai/v1',
++ docs_url: 'https://ui.infermatic.ai/docs', capabilities: ['chat'] },
++ { key: 'arliai', display_name: 'ArliAI', base_url: 'https://api.arliai.com/v1',
++ models_endpoint: 'https://api.arliai.com/model/all', models_public: true,
++ docs_url: 'https://www.arliai.com/', capabilities: ['chat', 'embed', 'image'] },
++ { key: 'friendli', display_name: 'Friendli', base_url: 'https://api.friendli.ai/serverless/v1', models_public: true,
++ docs_url: 'https://friendli.ai/docs/guides/model-apis/introduction', capabilities: ['chat'] },
++ { key: 'scaleway', display_name: 'Scaleway', base_url: 'https://api.scaleway.ai/v1',
++ docs_url: 'https://www.scaleway.com/en/developers/api/generative-apis', capabilities: ['chat', 'embed'] },
++
];
// The seed, console and personal-key forms consume these same records.
ddiff --git a/test/ai-provider-adapters.test.js b/test/ai-provider-adapters.test.js
iindex 4582eda..37b0401 100644
---- a/test/ai-provider-adapters.test.js
++++ b/test/ai-provider-adapters.test.js
@@@ -33,7 +33,9 @@ const DOMAIN = {
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',
++ baseten: 'baseten.co', hyperbolic: 'hyperbolic.xyz', parasail: 'parasail.io', 'particle-ai': 'particle.ai',
++ 'inference-net': 'inference.net', 'atlas-cloud': 'atlascloud.ai', avian: 'avian.io',
++ nanogpt: 'nano-gpt.com', infermatic: 'totalgpt.ai', arliai: 'arliai.com', friendli: 'friendli.ai', scaleway: 'scaleway.com',
};
const onItsOwnSite = (url, key) => {
const host = new URL(url).hostname;
@@@ -47,7 +49,7 @@ t.test('provider presets are OpenAI-compatible, with a base URL, a timeout, an a
assert.deepStrictEqual(pool.PROVIDERS.slice(0, 23).map((p) => p.key),
['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.strictEqual(new Set(pool.PROVIDER_KEYS).size, 40);
++ assert.strictEqual(new Set(pool.PROVIDER_KEYS).size, 50);
assert.strictEqual(pool.byKey.get('meta').base_url, 'https://api.meta.ai/v1');
assert.strictEqual(pool.byKey.get('perplexity').kind, 'responses');
assert.strictEqual(require('../server/console/presets').credentialProvider('groq').model, 'openai/gpt-oss-20b', 'existing personal-key default is preserved');
ddiff --git a/test/cheap-providers.test.js b/test/cheap-providers.test.js
nnew file mode 100644
iindex 0000000..0e31146
---- /dev/null
++++ b/test/cheap-providers.test.js
@@@ -0,0 +1,96 @@
++'use strict';
++/**
++ * The cheap hosted inference providers added 2026-11-10 (each endpoint read and probed keyless that day).
++ * Every new row starts disabled with no key and an https endpoint on its own documented host; sync imports
++ * a recorded models list (no network in tests) with prices from the provider's own list, models off.
++ */
++const assert = require('assert');
++const { boot, request, token, suite } = require('./helpers');
++const { PROVIDERS } = require('../server/providers/pool-providers');
++const seed = require('../server/providers/pool-seed');
++
++const t = suite('cheap-providers');
++const owner = token('exchange', ['ai.provider.manage', 'ai.usage.read']);
++
++const NEW_KEYS = ['parasail', 'particle-ai', 'inference-net', 'atlas-cloud', 'avian', 'nanogpt', 'infermatic', 'arliai', 'friendli', 'scaleway'];
++const HOST = {
++ parasail: 'api.parasail.io', 'particle-ai': 'api.particle.ai', 'inference-net': 'api.inference.net',
++ 'atlas-cloud': 'api.atlascloud.ai', avian: 'api.avian.io', nanogpt: 'api.nano-gpt.com',
++ infermatic: 'api.totalgpt.ai', arliai: 'api.arliai.com', friendli: 'api.friendli.ai', scaleway: 'api.scaleway.ai',
++};
++const KEYLESS = ['inference-net', 'atlas-cloud', 'avian', 'nanogpt', 'arliai', 'friendli'];
++
++const json = (body, status = 200) => new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });
++async function fetchImpl(input) {
++ const url = String(input);
++ if (url === 'https://api.inference.net/v1/models') return json({ object: 'list', data: [
++ { id: 'llama-3.3-70b', object: 'model', context_length: 131072, pricing: { prompt: '0.00000013', completion: '0.00000040', input_cache_read: '0.00000001' } },
++ { id: 'glm-5.3-flash', object: 'model', context_length: 1048576, pricing: { prompt: '0.00000010', completion: '0.00000045' } },
++ ] });
++ if (url === 'https://api.atlascloud.ai/v1/models') return json({ code: 200, msg: 'succeed', data: [
++ { id: 'deepseek-ai/DeepSeek-V3.2-Exp', object: 'model', context_length: 163840, quantization: 'fp8',
++ pricing: { prompt: '0.00000026', completion: '0.00000038', input_cache_read: '0.00000013' } },
++ ] });
++ return json({}, 404);
++}
++
++t.test('every new row: https endpoint on its own host, off, no key', () => {
++ for (const key of NEW_KEYS) {
++ const p = PROVIDERS.find((x) => x.key === key);
++ assert.ok(p, `${key} is catalogued`);
++ assert.strictEqual(new URL(p.base_url).hostname, HOST[key], `${key} base host`);
++ assert.ok(p.base_url.startsWith('https://'), `${key} https`);
++ assert.ok(p.metadata === undefined || typeof p === 'object', `${key} record`);
++ const row = seed.recordOf(p);
++ assert.strictEqual(row.status, 'disabled', `${key} starts off`);
++ assert.strictEqual(row.secret_ref, `vault:${key}`, `${key} key slot named, no key`);
++ assert.strictEqual(row.metadata.api_family, 'openai', `${key} OpenAI wire format`);
++ assert.ok(new URL(row.metadata.models_endpoint).hostname === HOST[key] || key === 'arliai', `${key} models on its own host`);
++ }
++ // Key-required lists stay out of the keyless sync, keyless ones join it.
++ for (const key of ['parasail', 'particle-ai', 'infermatic', 'scaleway'])
++ assert.notStrictEqual(PROVIDERS.find((x) => x.key === key).models_public, true, `${key} needs a key for its list`);
++ for (const key of KEYLESS)
++ assert.strictEqual(PROVIDERS.find((x) => x.key === key).models_public, true, `${key} list answers keyless`);
++});
++
++let h;
++t.test('boot seeds the new providers disabled and credentialless; sync imports prices from the recorded list', async () => {
++ h = await boot({ fetchImpl, env: { AI_USER_MODULES: 'off' } });
++ for (const key of NEW_KEYS) {
++ const p = await h.registry.getProvider(key);
++ assert.ok(p, `${key} seeded`);
++ assert.strictEqual(p.status, 'disabled', `${key} off`);
++ assert.strictEqual(p.secret_ref, `vault:${key}`, `${key} key slot`);
++ assert.strictEqual(p.metadata.models_public === true, KEYLESS.includes(key), `${key} keyless list flag`);
++ }
++ const pub = await request(h.base, 'GET', '/api/v1/providers/inference-net', { tok: owner });
++ assert.strictEqual(pub.body.provider.credentials, 'missing', 'no key configured');
++ assert.strictEqual(pub.body.provider.secret_ref, 'vault:inference-net', 'only the slot name is exposed, never a value');
++
++ const syncOpts = { actor: 'cheap-providers-test' };
++ const meta = { models_endpoint: 'https://api.inference.net/v1/models' };
++ const provider = await h.registry.getProvider('inference-net');
++ const { sync } = require('../server/providers/catalog');
++ const result = await sync({ registry: h.registry, provider: { ...provider, metadata: { ...provider.metadata, ...meta } }, apiKey: null, fetchImpl, actor: 'cheap-providers-test' });
++ assert.deepStrictEqual(result.added.sort(), ['glm-5.3-flash', 'llama-3.3-70b']);
++ const llama = await h.registry.getModel('inference-net', 'llama-3.3-70b');
++ assert.strictEqual(llama.status, 'disabled', 'synced models arrive off');
++ const near = (a, b) => Math.abs(a - b) < 1e-9;
++ assert.ok(near(llama.cost.in_per_mtok, 0.13), `prompt per-token string read as per-MTok, got ${llama.cost.in_per_mtok}`);
++ assert.ok(near(llama.cost.out_per_mtok, 0.40), `got ${llama.cost.out_per_mtok}`);
++ assert.ok(near(llama.cost.cached_per_mtok, 0.01), `got ${llama.cost.cached_per_mtok}`);
++ assert.strictEqual(llama.cost.source, 'https://api.inference.net/v1/models', 'price source is the provider list');
++ assert.strictEqual(llama.context_window, 131072);
++
++ const atlas = await h.registry.getProvider('atlas-cloud');
++ const atlasResult = await sync({ registry: h.registry, provider: { ...atlas, metadata: { ...atlas.metadata, models_endpoint: 'https://api.atlascloud.ai/v1/models' } }, apiKey: null, fetchImpl, actor: 'cheap-providers-test' });
++ assert.deepStrictEqual(atlasResult.added, ['deepseek-ai/DeepSeek-V3.2-Exp']);
++ const deepseek = await h.registry.getModel('atlas-cloud', 'deepseek-ai/DeepSeek-V3.2-Exp');
++ assert.strictEqual(deepseek.status, 'disabled');
++ assert.ok(near(deepseek.cost.in_per_mtok, 0.26), `got ${deepseek.cost.in_per_mtok}`);
++ assert.ok(near(deepseek.cost.out_per_mtok, 0.38), `got ${deepseek.cost.out_per_mtok}`);
++ assert.strictEqual(deepseek.context_window, 163840);
++});
++
++t.run(() => h && h.stop());