openvibers/openvibe.ai Commits
Add Meta Muse models with owner-off routing (#458)
Changes
@@@ -211,10 +211,12 @@ function createCatalog({ db, registry, pool, env = process.env, clock = { now: (const provider = byKey.get(row.provider_key);if (!provider || provider.kind === 'stub' || (provider.metadata && provider.metadata.synthetic)) continue;if (provider.metadata && provider.metadata.hidden === true) continue;-- if (row.status !== 'active' || provider.status !== 'active') continue;++ if (row.status !== 'active' || (provider.status !== 'active' && provider.key !== 'meta')) continue;// A model is an offer only when the key its provider needs is configured now. A record with no// usable upstream (the internal `shared` label with no real provider behind it) is never shown.-- if (!upstream.configured(provider, env)) continue;++ // Meta's hand-kept Muse entries remain visible with their published prices while the owner switch is off.++ // They are explicitly marked down below and cannot be placed by the router until the provider is enabled.++ if (!upstream.configured(provider, env) && (provider.key !== 'meta' || provider.status === 'active')) continue;const up = upstreamByKey.get(provider.key);if (!up || !upstream.serves(up, provider, row)) continue;// Capacity the network may not resell is never an offer: a person's own key, or a restricted one.@@@ -253,8 +255,8 @@ function createCatalog({ db, registry, pool, env = process.env, clock = { now: (// output price is at or below AI_FREE_MAX_OUTPUT_PRICE_USD_PER_MTOK.free_tier: allowsOffer({ providerKey: provider.key, providerRecord: provider, outputPrice: price ? price.out : null }, freeUse),// up | degraded | down, measured from this offer's circuit and the last hour of requests.-- status: health.status,-- status_reason: health.status_reason,++ status: provider.status === 'active' ? health.status : 'down',++ status_reason: provider.status === 'active' ? health.status_reason : 'off until the owner turns it on',status_since: health.status_since,status_class: health.status_class,// Rounded to the micro-dollar: a derived cached price was printed as 0.020000000000000004.@@@ -22,6 +22,8 @@ const { ProviderError, deadline, describeAbort } = require('./common');// Each family: the id it matches, then what it is. First match wins, so the narrower come first.const FAMILIES = [++ [/^muse-ember/i, { tier: 'fast', vision: true, tools: true, reasoning: true, note: 'high-volume repeated work and agent sub-steps' }],++ [/^muse-spark/i, { tier: 'strong', vision: true, tools: true, reasoning: true, note: 'general and agentic work' }],[/rerank/i, { type: 'rerank', tier: 'fast' }],[/tts|text-to-speech|speech-/i, { type: 'speech', tier: 'fast' }],[/embed/i, { type: 'embedding', tier: 'fast' }],@@@ -174,7 +176,9 @@ async function sync({ registry, provider, apiKey, fetchImpl = globalThis.fetch,seen.add(m.id);const prev = known.get(m.id);const uncensored = require('./pool-providers').isUncensored(provider, { model_key: m.id, metadata: prev?.metadata || {} });++ const contributor = provider.key === 'meta' && /-contributor$/i.test(m.id);const metadata = { tier: d.tier, reasoning: d.reasoning, note: d.note, ...prev?.metadata, synced_at: at, listed: true,++ ...(contributor ? { routing_excluded: true, routing_excluded_reason: 'Meta Contributor mode permits training on prompts and is not allowed in OpenVibe routing.' } : {}),...(uncensored ? { uncensored: true, min_age: 18 } : {}),...(extra.source ? { catalogue_enrichment: { source: extra.source, checked_at: extra.checked_at } } : {}) };delete metadata.missing_since;@@@ -184,7 +188,7 @@ async function sync({ registry, provider, apiKey, fetchImpl = globalThis.fetch,await registry.upsertModel({provider_key: provider.key, model_key: m.id, display_name: prev?.display_name || m.name || m.id, type: d.type,// A new model is switched off until a person turns it on; a known one keeps its status (and its prices).-- status: prev ? prev.status : provider.metadata?.auto_list_new_models === true && !uncensored ? 'active' : 'disabled',++ status: contributor ? 'disabled' : prev ? prev.status : provider.metadata?.auto_list_new_models === true && !uncensored ? 'active' : 'disabled',context_window: d.contextWindow ?? (prev && prev.context_window) ?? null, max_output: d.maxOutput ?? (prev && prev.max_output) ?? null,supports: { json: true, tools: d.tools, streaming: true, vision: d.vision }, metadata, ...(cost ? { cost } : {}),}, { actor });@@@ -33,6 +33,7 @@ const { retryable, sleep, ProviderError, schemaInstruction, insufficientBalanceconst { classifyFailure, reasonForClass, circuitStatus, offerStatus, iso } = require('./failure');const { createOwnerNotifier } = require('./owner-notify');const { sourcesOf, ceilingOf, allowsProvider, allowsCommunity } = require('../free-tier');++const { isRoutingExcludedModel } = require('./pool-providers');const { createStubProvider } = require('./stub');const { createOpenAiProvider } = require('./openai');const { createResponsesProvider } = require('./responses');@@@ -676,7 +677,9 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no}async function costOf(providerKey, model, usage) {const cards = await rateCardsFor(providerKey, model);-- return cards ? rates.costOfUsage(cards, usage) : 0;++ if (!cards) return 0;++ const longContext = providerKey === 'meta' && model === 'muse-ember-1.0' && Number(usage && usage.input) > 200000;++ return rates.costOfUsage(longContext ? rates.applyMultiplier(cards, 3, 'long_context') : cards, usage);}// ── The tier a run is priced on (BA1) ────────────────────────────@@@ -773,7 +776,10 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.noasync function costOnTier({ providerKey, model, usage, tier, p }) {const cards = await rateCardsFor(providerKey, model);if (!cards) return { cost: 0, standard: 0, saved: 0 };-- const standard = rates.costOfUsage(cards, usage);++ // Meta's Muse Ember long-context tier applies to the whole request when input (cached included) exceeds 200K.++ const longContext = providerKey === 'meta' && model === 'muse-ember-1.0' && Number(usage && usage.input) > 200000;++ const billable = longContext ? rates.applyMultiplier(cards, 3, 'long_context') : cards;++ const standard = rates.costOfUsage(billable, usage);if (tier === 'standard' || tier === 'local') return { cost: standard, standard, saved: 0 };if (tier === 'offpeak') {const off = offPeak ? await offPeak.priceAt(providerKey, model, clock.now()) : null;@@@ -954,7 +960,9 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no// A trait keeps the offer out of the call, not out of placement: it stays in the pool with its// reason (status down, like a credential-less offer), so explain has one entry per placement offer.let traitReason = null;-- if (!traits.latencyAllows(p, row, latencyClass, 'standard', { allowLimitedInteractive, allowFlexInteractive })) {++ if (isRoutingExcludedModel(model, row)) {++ traitReason = 'model is explicitly excluded from routing';++ } else if (!traits.latencyAllows(p, row, latencyClass, 'standard', { allowLimitedInteractive, allowFlexInteractive })) {traitReason = `latency_class ${latencyClass} not offered (${(traits.latencyClassesOf(p, row) || []).join('/') || 'interactive only'})`;} else if (!allowPrivateTraining && traits.isPrivateRequest(ctx) && traits.trainsOrRetains(p)) {traitReason = 'private data: the provider trains on or retains prompts';@@@ -1118,17 +1126,19 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.noconst access = accessOffered(p);const policyOk = p ? await policyAllows(registry, p, modelId, ctx.contentPolicy || 'standard') : false;const row = p && modelId ? await registry.getModel(p.key, modelId) : null;++ const excludedModel = isRoutingExcludedModel(modelId, row);const latencyOk = p ? traits.latencyAllows(p, row, (ctx.latencyClass && ['interactive', 'background', 'batch'].includes(ctx.latencyClass)) ? ctx.latencyClass : ((route.constraints && route.constraints.latency_class) || 'interactive'), 'standard', { allowLimitedInteractive, allowFlexInteractive }) : false;const privateOk = p ? !(!allowPrivateTraining && traits.isPrivateRequest(ctx) && traits.trainsOrRetains(p)) : false;-- const excluded = !policyOk ? 'content_policy'++ const excluded = excludedModel ? 'model is explicitly excluded from routing'++ : !policyOk ? 'content_policy': !offered ? (p && billingProfile(p) === 'byok' ? 'byok' : 'free_only'): !access ? `provider_access ${modelCatalog.accessOf(p)}` : !latencyOk ? 'latency_class': !privateOk ? 'private_data_training' : why;explain.candidates.push({-- provider: c.provider, model: modelId, eligible: offered && access && policyOk && latencyOk && privateOk && !why,++ provider: c.provider, model: modelId, eligible: !excludedModel && offered && access && policyOk && latencyOk && privateOk && !why,excluded_reason: excluded, cost: null, latency: null,});-- if (offered && access && policyOk && latencyOk && privateOk) order.push({ provider: c.provider, model: c.model || null });++ if (!excludedModel && offered && access && policyOk && latencyOk && privateOk) order.push({ provider: c.provider, model: c.model || null });}return { order, explain };}@@@ -710,10 +710,32 @@ const PROVIDERS = [],};}),-- // Providers without a hand-kept model list: the owner syncs and enables their models.-- { key: 'meta', display_name: 'Meta Model API', base_url: 'https://api.meta.ai/v1',-- docs_url: 'https://dev.meta.ai/docs/api-reference', capabilities: ['chat', 'vision', 'tools', 'web-search'],-- search_adapters: ['meta'] },++ // Meta Model API (prices/specs read from Meta's own pages on 2026-11-10). Contributor variants are++ // intentionally absent: they permit Meta to train on prompts and must never enter the managed pool.++ { key: 'meta', keys_url: 'https://dev.meta.ai/', display_name: 'Meta Model API', base_url: 'https://api.meta.ai/v1',++ default_model: 'muse-spark-1.3', docs_url: 'https://dev.meta.ai/docs/api-reference',++ capabilities: ['chat', 'vision', 'tools', 'json', 'web-search'], search_adapters: ['meta'],++ traits: traits({ latency_classes: ['interactive', 'background', 'batch'], source: 'https://dev.meta.ai/docs/pricing-rate-limits', checked: '2026-11-10' }),++ privacy: privacy({ label: 'anonymized', terms_url: 'https://dev.meta.ai/docs/pricing-rate-limits',++ wording: 'Standard mode content is not used to train Meta models; Contributor mode is excluded.', checked: '2026-11-10' }),++ data_policy: policy({ trains: false, retention: null, trust: 'first-party',++ retention_note: 'Standard mode content is not used to train Meta models; retention is not stated on the pricing page.',++ terms: 'https://dev.meta.ai/docs/pricing-rate-limits', checked: '2026-11-10' }),++ models: [++ { model_key: 'muse-ember-1.0', display_name: 'Muse Ember 1.0', family: 'muse-ember', context_window: 1048576, max_output: 524288,++ cost: { in_per_mtok: 0.10, cached_per_mtok: 0.004, out_per_mtok: 0.50 },++ source: 'https://dev.meta.ai/docs/pricing-rate-limits', checked: '2026-11-10',++ supports: { json: true, tools: true, streaming: true, vision: true },++ quantization: null, refusal_rate: null,++ note: 'Standard mode only; reasoning effort minimal to max (default max). Requests above 200K input tokens bill the entire request at 3x: $0.30 input, $0.012 cached input, $1.50 output per 1M.' },++ { model_key: 'muse-spark-1.3', display_name: 'Muse Spark 1.3', family: 'muse-spark', context_window: 1048576,++ cost: { in_per_mtok: 1.25, cached_per_mtok: 0.15, out_per_mtok: 4.25 },++ source: 'https://dev.meta.ai/docs/pricing-rate-limits', checked: '2026-11-10',++ supports: { json: true, tools: true, streaming: true, vision: true },++ quantization: null, refusal_rate: null,++ note: 'Standard mode only; reasoning effort minimal to max. Contributor mode is excluded.' },++ ],++ },{ key: 'deepseek', keys_url: 'https://platform.deepseek.com/api_keys', display_name: 'DeepSeek', base_url: 'https://api.deepseek.com/v1', default_model: 'deepseek-flash',docs_url: 'https://api-docs.deepseek.com/', capabilities: ['chat', 'vision', 'tools', 'web-search'], search_adapters: ['deepseek'] },{ key: 'anthropic', keys_url: 'https://console.anthropic.com/settings/keys', display_name: 'Anthropic', kind: 'anthropic', base_url: 'https://api.anthropic.com/v1', auth_mode: 'x-api-key',@@@ -985,4 +1007,9 @@ function isUncensored(provider, model) {return /abliterated|uncensored/i.test(id);}--module.exports = { PROVIDERS, PROVIDER_KEYS, CHECKED, byKey, priceOf, isUncensored, leaf };++/** Contributor variants let Meta train on prompts and must never enter OpenVibe routing. */++function isRoutingExcludedModel(modelKey, model = null) {++ return /-contributor$/i.test(String(modelKey || '')) || Boolean(model && model.metadata && model.metadata.routing_excluded === true);++}++++module.exports = { PROVIDERS, PROVIDER_KEYS, CHECKED, byKey, priceOf, isUncensored, isRoutingExcludedModel, leaf };@@@ -36,7 +36,7 @@ const DOMAIN = {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',-- iteracompute: 'iteracompute.com', sable: 'sableapi.com', inception: 'inceptionlabs.ai', dosai: 'dos.ai', 'runpod-vllm': 'runpod.io',++ iteracompute: 'iteracompute.com', sable: 'sableapi.com', inception: 'inceptionlabs.ai', dosai: 'dos.ai', 'runpod-vllm': 'runpod.io', meta: 'meta.ai',};const onItsOwnSite = (url, key) => {const host = new URL(url).hostname;nnew file mode 100644@@@ -0,0 +1,108 @@++'use strict';++const assert = require('assert');++const { PROVIDERS, isRoutingExcludedModel } = require('../server/providers/pool-providers');++const seed = require('../server/providers/pool-seed');++const { describe } = require('../server/providers/catalog');++const { checkProvider } = require('../server/providers/check');++const { boot, suite, seamServer } = require('./helpers');++const t = suite('provider-muse');++++const meta = PROVIDERS.find((p) => p.key === 'meta');++const model = (key) => meta.models.find((m) => m.model_key === key);++++t.test('Meta Muse records are dated, standard mode, priced, capable, and seeded off', () => {++ assert.ok(meta);++ const p = seed.recordOf(meta);++ assert.strictEqual(p.status, 'disabled');++ assert.strictEqual(p.default_model, 'muse-spark-1.3');++ assert.strictEqual(p.metadata.data_policy.trains, false);++ assert.deepStrictEqual(meta.models.map((m) => m.model_key), ['muse-ember-1.0', 'muse-spark-1.3']);++ const ember = model('muse-ember-1.0');++ assert.deepStrictEqual(ember.cost, { in_per_mtok: 0.10, cached_per_mtok: 0.004, out_per_mtok: 0.50 });++ assert.match(ember.note, /above 200K input tokens bill the entire request at 3x/);++ assert.strictEqual(ember.checked, '2026-11-10');++ assert.strictEqual(ember.context_window, 1048576);++ assert.strictEqual(ember.max_output, 524288);++ assert.deepStrictEqual(ember.supports, { json: true, tools: true, streaming: true, vision: true });++ assert.deepStrictEqual(model('muse-spark-1.3').cost, { in_per_mtok: 1.25, cached_per_mtok: 0.15, out_per_mtok: 4.25 });++ assert.strictEqual(model('muse-spark-1.3').checked, '2026-11-10');++ assert.strictEqual(describe('muse-ember-1.0').tier, 'fast');++ assert.strictEqual(describe('muse-ember-1.0').reasoning, true);++ assert.strictEqual(describe('muse-spark-1.3').tier, 'strong');++ assert.strictEqual(describe('muse-spark-1.3').tools, true);++ assert.ok(!meta.models.some((m) => /wonder-full|contributor/i.test(m.model_key)));++});++++t.test('Contributor model variants are excluded even if manually activated', () => {++ assert.strictEqual(isRoutingExcludedModel('muse-spark-1.3-contributor'), true);++ assert.strictEqual(isRoutingExcludedModel('muse-spark-1.3', { metadata: { routing_excluded: true } }), true);++ assert.strictEqual(isRoutingExcludedModel('muse-spark-1.3'), false);++});++++t.test('Exchange catalog carries Muse prices and an explicit off state before owner enablement', async () => {++ const { boot } = require('./helpers');++ const host = await boot({ env: { AI_STUB_FALLBACK: 'false' } });++ try {++ await host.registry.upsertProvider({ ...seed.recordOf(meta), auth_mode: 'bearer', status: 'disabled', secret_ref: 'env:META_CATALOG_KEY' });++ for (const m of meta.models) await host.registry.upsertModel(seed.modelOf(meta, m));++ const env = { META_CATALOG_KEY: 'synthetic-meta-catalog-key' };++ const catalog = require('../server/exchange/catalog').createCatalog({ db: host.db, registry: host.registry, pool: host.pool, env, config: host.config });++ const out = await catalog.models();++ for (const [id, expected] of [['muse-ember-1.0', 0.10], ['muse-spark-1.3', 1.25]]) {++ const entry = out.models.find((m) => m.slug === `meta/${id}`);++ assert.ok(entry, `${id} is listed`);++ assert.strictEqual(entry.offers[0].price.in, expected);++ assert.strictEqual(entry.offers[0].status, 'down');++ assert.strictEqual(entry.offers[0].status_reason, 'off until the owner turns it on');++ }++ const under = await host.pool.costOf('meta', 'muse-ember-1.0', { input: 200000, cached: 100000, output: 100000 });++ const over = await host.pool.costOf('meta', 'muse-ember-1.0', { input: 200001, cached: 100000, output: 100000 });++ assert.ok(Math.abs(under - 0.0604) < 1e-10, `200K uses standard rates across uncached, cached and output tokens: ${under}`);++ assert.ok(Math.abs(over - 0.1812003) < 1e-10, `over 200K scales the entire request, not only excess tokens: ${over}`);++ await host.registry.upsertProvider({ key: 'meta', status: 'active' });++ const choices = require('../server/choices').createChoices({ db: host.db, registry: host.registry, catalog, ratings: async () => [] });++ await choices.seed();++ const chip = await choices.get('chat.reply');++ const ember = chip.options.openvibe.filter((row) => row.model === 'meta/muse-ember-1.0');++ const spark = chip.options.openvibe.filter((row) => row.model === 'meta/muse-spark-1.3');++ assert.strictEqual(ember.length, 1, 'the shared chip receives Ember once through its live options feed');++ assert.strictEqual(spark.length, 1, 'the shared chip receives Spark once through its live options feed');++ } finally { await host.stop(); }++});++++let h;++let seam;++t.test('router skips a manually activated Contributor model and uses the eligible fallback', async () => {++ seam = await seamServer(() => ({ text: 'eligible answer', model: 'fallback-model', usage: { input: 2, output: 2 } }));++ h = await boot({ env: { AI_STUB_FALLBACK: 'false' } });++ const addProvider = (key) => h.registry.upsertProvider({ key, kind: 'http', auth_mode: 'none', status: 'active', base_url: seam.url, capabilities: ['chat'], priority: 50 });++ const addModel = (provider_key, model_key, metadata = {}) => h.registry.upsertModel({ provider_key, model_key, status: 'active', cost: { in_per_mtok: 0.01, out_per_mtok: 0.01 }, metadata, supports: { streaming: true } });++ await addProvider('meta');++ await addModel('meta', 'muse-spark-1.3-contributor');++ await addProvider('fallback-open');++ await addModel('fallback-open', 'fallback-model');++ const route = { key: 'muse-contributor', primary: { provider: 'meta', model: 'muse-spark-1.3-contributor' }, fallbacks: [{ provider: 'fallback-open', model: 'fallback-model' }] };++ const result = await h.pool.execute(route, 'chat', { messages: [{ role: 'user', content: 'hello' }], maxTokens: 4 });++ assert.strictEqual(result.provider, 'fallback-open');++ assert.ok(result.explain.candidates.some((c) => c.provider === 'meta' && /excluded from routing/.test(c.excluded_reason || '')));++});++++t.test('the owner check path sends one tiny model-list read and never exposes credentials', async () => {++ let count = 0;++ const result = await checkProvider({ key: 'meta', auth_mode: 'bearer', secret_ref: 'env:META_TEST_KEY', base_url: 'https://api.meta.ai/v1', kind: 'openai' }, {++ env: { META_TEST_KEY: 'synthetic-meta-key-not-a-real-secret' },++ fetchImpl: async (url, init) => {++ count++;++ assert.strictEqual(String(url), 'https://api.meta.ai/v1/models');++ assert.strictEqual(init.method, 'GET');++ assert.strictEqual(init.headers.Authorization, 'Bearer synthetic-meta-key-not-a-real-secret');++ return new Response(JSON.stringify({ data: [{ id: 'muse-ember-1.0' }] }), { status: 200, headers: { 'content-type': 'application/json' } });++ },++ });++ assert.strictEqual(count, 1);++ assert.strictEqual(result.ok, true);++ assert.strictEqual(result.requests, 1);++ assert.ok(!JSON.stringify(result).includes('synthetic-meta-key'));++});++++t.run();
@@ -211,10 +211,12 @@ function createCatalog({ db, registry, pool, env = process.env, clock = { now: (
@@ -211,10 +211,12 @@ function createCatalog({ db, registry, pool, env = process.env, clock = { now: (
const provider = byKey.get(row.provider_key);
const provider = byKey.get(row.provider_key);
if (!provider || provider.kind === 'stub' || (provider.metadata && provider.metadata.synthetic)) continue;
if (!provider || provider.kind === 'stub' || (provider.metadata && provider.metadata.synthetic)) continue;
if (provider.metadata && provider.metadata.hidden === true) continue;
if (provider.metadata && provider.metadata.hidden === true) continue;
- if (row.status !== 'active' || provider.status !== 'active') continue;
+ if (row.status !== 'active' || (provider.status !== 'active' && provider.key !== 'meta')) continue;
// A model is an offer only when the key its provider needs is configured now. A record with no
// A model is an offer only when the key its provider needs is configured now. A record with no
// usable upstream (the internal `shared` label with no real provider behind it) is never shown.
// usable upstream (the internal `shared` label with no real provider behind it) is never shown.
- if (!upstream.configured(provider, env)) continue;
+ // Meta's hand-kept Muse entries remain visible with their published prices while the owner switch is off.
+ // They are explicitly marked down below and cannot be placed by the router until the provider is enabled.
+ if (!upstream.configured(provider, env) && (provider.key !== 'meta' || provider.status === 'active')) continue;
const up = upstreamByKey.get(provider.key);
const up = upstreamByKey.get(provider.key);
if (!up || !upstream.serves(up, provider, row)) continue;
if (!up || !upstream.serves(up, provider, row)) continue;
// Capacity the network may not resell is never an offer: a person's own key, or a restricted one.
// Capacity the network may not resell is never an offer: a person's own key, or a restricted one.
@@ -253,8 +255,8 @@ function createCatalog({ db, registry, pool, env = process.env, clock = { now: (
@@ -253,8 +255,8 @@ function createCatalog({ db, registry, pool, env = process.env, clock = { now: (
// output price is at or below AI_FREE_MAX_OUTPUT_PRICE_USD_PER_MTOK.
// output price is at or below AI_FREE_MAX_OUTPUT_PRICE_USD_PER_MTOK.
free_tier: allowsOffer({ providerKey: provider.key, providerRecord: provider, outputPrice: price ? price.out : null }, freeUse),
free_tier: allowsOffer({ providerKey: provider.key, providerRecord: provider, outputPrice: price ? price.out : null }, freeUse),
// up | degraded | down, measured from this offer's circuit and the last hour of requests.
// up | degraded | down, measured from this offer's circuit and the last hour of requests.
- status: health.status,
- status_reason: health.status_reason,
+ status: provider.status === 'active' ? health.status : 'down',
+ status_reason: provider.status === 'active' ? health.status_reason : 'off until the owner turns it on',
status_since: health.status_since,
status_since: health.status_since,
status_class: health.status_class,
status_class: health.status_class,
// Rounded to the micro-dollar: a derived cached price was printed as 0.020000000000000004.
// Rounded to the micro-dollar: a derived cached price was printed as 0.020000000000000004.
@@ -22,6 +22,8 @@ const { ProviderError, deadline, describeAbort } = require('./common');
@@ -22,6 +22,8 @@ const { ProviderError, deadline, describeAbort } = require('./common');
// Each family: the id it matches, then what it is. First match wins, so the narrower come first.
// Each family: the id it matches, then what it is. First match wins, so the narrower come first.
const FAMILIES = [
const FAMILIES = [
+ [/^muse-ember/i, { tier: 'fast', vision: true, tools: true, reasoning: true, note: 'high-volume repeated work and agent sub-steps' }],
+ [/^muse-spark/i, { tier: 'strong', vision: true, tools: true, reasoning: true, note: 'general and agentic work' }],
[/rerank/i, { type: 'rerank', tier: 'fast' }],
[/rerank/i, { type: 'rerank', tier: 'fast' }],
[/tts|text-to-speech|speech-/i, { type: 'speech', tier: 'fast' }],
[/tts|text-to-speech|speech-/i, { type: 'speech', tier: 'fast' }],
[/embed/i, { type: 'embedding', tier: 'fast' }],
[/embed/i, { type: 'embedding', tier: 'fast' }],
@@ -174,7 +176,9 @@ async function sync({ registry, provider, apiKey, fetchImpl = globalThis.fetch,
@@ -174,7 +176,9 @@ async function sync({ registry, provider, apiKey, fetchImpl = globalThis.fetch,
seen.add(m.id);
seen.add(m.id);
const prev = known.get(m.id);
const prev = known.get(m.id);
const uncensored = require('./pool-providers').isUncensored(provider, { model_key: m.id, metadata: prev?.metadata || {} });
const uncensored = require('./pool-providers').isUncensored(provider, { model_key: m.id, metadata: prev?.metadata || {} });
+ const contributor = provider.key === 'meta' && /-contributor$/i.test(m.id);
const metadata = { tier: d.tier, reasoning: d.reasoning, note: d.note, ...prev?.metadata, synced_at: at, listed: true,
const metadata = { tier: d.tier, reasoning: d.reasoning, note: d.note, ...prev?.metadata, synced_at: at, listed: true,
+ ...(contributor ? { routing_excluded: true, routing_excluded_reason: 'Meta Contributor mode permits training on prompts and is not allowed in OpenVibe routing.' } : {}),
...(uncensored ? { uncensored: true, min_age: 18 } : {}),
...(uncensored ? { uncensored: true, min_age: 18 } : {}),
...(extra.source ? { catalogue_enrichment: { source: extra.source, checked_at: extra.checked_at } } : {}) };
...(extra.source ? { catalogue_enrichment: { source: extra.source, checked_at: extra.checked_at } } : {}) };
delete metadata.missing_since;
delete metadata.missing_since;
@@ -184,7 +188,7 @@ async function sync({ registry, provider, apiKey, fetchImpl = globalThis.fetch,
@@ -184,7 +188,7 @@ async function sync({ registry, provider, apiKey, fetchImpl = globalThis.fetch,
await registry.upsertModel({
await registry.upsertModel({
provider_key: provider.key, model_key: m.id, display_name: prev?.display_name || m.name || m.id, type: d.type,
provider_key: provider.key, model_key: m.id, display_name: prev?.display_name || m.name || m.id, type: d.type,
// A new model is switched off until a person turns it on; a known one keeps its status (and its prices).
// A new model is switched off until a person turns it on; a known one keeps its status (and its prices).
- status: prev ? prev.status : provider.metadata?.auto_list_new_models === true && !uncensored ? 'active' : 'disabled',
+ status: contributor ? 'disabled' : prev ? prev.status : provider.metadata?.auto_list_new_models === true && !uncensored ? 'active' : 'disabled',
context_window: d.contextWindow ?? (prev && prev.context_window) ?? null, max_output: d.maxOutput ?? (prev && prev.max_output) ?? null,
context_window: d.contextWindow ?? (prev && prev.context_window) ?? null, max_output: d.maxOutput ?? (prev && prev.max_output) ?? null,
supports: { json: true, tools: d.tools, streaming: true, vision: d.vision }, metadata, ...(cost ? { cost } : {}),
supports: { json: true, tools: d.tools, streaming: true, vision: d.vision }, metadata, ...(cost ? { cost } : {}),
}, { actor });
}, { actor });
@@ -33,6 +33,7 @@ const { retryable, sleep, ProviderError, schemaInstruction, insufficientBalance
@@ -33,6 +33,7 @@ const { retryable, sleep, ProviderError, schemaInstruction, insufficientBalance
const { classifyFailure, reasonForClass, circuitStatus, offerStatus, iso } = require('./failure');
const { classifyFailure, reasonForClass, circuitStatus, offerStatus, iso } = require('./failure');
const { createOwnerNotifier } = require('./owner-notify');
const { createOwnerNotifier } = require('./owner-notify');
const { sourcesOf, ceilingOf, allowsProvider, allowsCommunity } = require('../free-tier');
const { sourcesOf, ceilingOf, allowsProvider, allowsCommunity } = require('../free-tier');
+const { isRoutingExcludedModel } = require('./pool-providers');
const { createStubProvider } = require('./stub');
const { createStubProvider } = require('./stub');
const { createOpenAiProvider } = require('./openai');
const { createOpenAiProvider } = require('./openai');
const { createResponsesProvider } = require('./responses');
const { createResponsesProvider } = require('./responses');
@@ -676,7 +677,9 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no
@@ -676,7 +677,9 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no
}
}
async function costOf(providerKey, model, usage) {
async function costOf(providerKey, model, usage) {
const cards = await rateCardsFor(providerKey, model);
const cards = await rateCardsFor(providerKey, model);
- return cards ? rates.costOfUsage(cards, usage) : 0;
+ if (!cards) return 0;
+ const longContext = providerKey === 'meta' && model === 'muse-ember-1.0' && Number(usage && usage.input) > 200000;
+ return rates.costOfUsage(longContext ? rates.applyMultiplier(cards, 3, 'long_context') : cards, usage);
}
}
// ── The tier a run is priced on (BA1) ────────────────────────────
// ── The tier a run is priced on (BA1) ────────────────────────────
@@ -773,7 +776,10 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no
@@ -773,7 +776,10 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no
async function costOnTier({ providerKey, model, usage, tier, p }) {
async function costOnTier({ providerKey, model, usage, tier, p }) {
const cards = await rateCardsFor(providerKey, model);
const cards = await rateCardsFor(providerKey, model);
if (!cards) return { cost: 0, standard: 0, saved: 0 };
if (!cards) return { cost: 0, standard: 0, saved: 0 };
- const standard = rates.costOfUsage(cards, usage);
+ // Meta's Muse Ember long-context tier applies to the whole request when input (cached included) exceeds 200K.
+ const longContext = providerKey === 'meta' && model === 'muse-ember-1.0' && Number(usage && usage.input) > 200000;
+ const billable = longContext ? rates.applyMultiplier(cards, 3, 'long_context') : cards;
+ const standard = rates.costOfUsage(billable, usage);
if (tier === 'standard' || tier === 'local') return { cost: standard, standard, saved: 0 };
if (tier === 'standard' || tier === 'local') return { cost: standard, standard, saved: 0 };
if (tier === 'offpeak') {
if (tier === 'offpeak') {
const off = offPeak ? await offPeak.priceAt(providerKey, model, clock.now()) : null;
const off = offPeak ? await offPeak.priceAt(providerKey, model, clock.now()) : null;
@@ -954,7 +960,9 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no
@@ -954,7 +960,9 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no
// A trait keeps the offer out of the call, not out of placement: it stays in the pool with its
// A trait keeps the offer out of the call, not out of placement: it stays in the pool with its
// reason (status down, like a credential-less offer), so explain has one entry per placement offer.
// reason (status down, like a credential-less offer), so explain has one entry per placement offer.
let traitReason = null;
let traitReason = null;
- if (!traits.latencyAllows(p, row, latencyClass, 'standard', { allowLimitedInteractive, allowFlexInteractive })) {
+ if (isRoutingExcludedModel(model, row)) {
+ traitReason = 'model is explicitly excluded from routing';
+ } else if (!traits.latencyAllows(p, row, latencyClass, 'standard', { allowLimitedInteractive, allowFlexInteractive })) {
traitReason = `latency_class ${latencyClass} not offered (${(traits.latencyClassesOf(p, row) || []).join('/') || 'interactive only'})`;
traitReason = `latency_class ${latencyClass} not offered (${(traits.latencyClassesOf(p, row) || []).join('/') || 'interactive only'})`;
} else if (!allowPrivateTraining && traits.isPrivateRequest(ctx) && traits.trainsOrRetains(p)) {
} else if (!allowPrivateTraining && traits.isPrivateRequest(ctx) && traits.trainsOrRetains(p)) {
traitReason = 'private data: the provider trains on or retains prompts';
traitReason = 'private data: the provider trains on or retains prompts';
@@ -1118,17 +1126,19 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no
@@ -1118,17 +1126,19 @@ function createProviderPool({ db, registry, config, clock = { now: () => Date.no
const access = accessOffered(p);
const access = accessOffered(p);
const policyOk = p ? await policyAllows(registry, p, modelId, ctx.contentPolicy || 'standard') : false;
const policyOk = p ? await policyAllows(registry, p, modelId, ctx.contentPolicy || 'standard') : false;
const row = p && modelId ? await registry.getModel(p.key, modelId) : null;
const row = p && modelId ? await registry.getModel(p.key, modelId) : null;
+ const excludedModel = isRoutingExcludedModel(modelId, row);
const latencyOk = p ? traits.latencyAllows(p, row, (ctx.latencyClass && ['interactive', 'background', 'batch'].includes(ctx.latencyClass)) ? ctx.latencyClass : ((route.constraints && route.constraints.latency_class) || 'interactive'), 'standard', { allowLimitedInteractive, allowFlexInteractive }) : false;
const latencyOk = p ? traits.latencyAllows(p, row, (ctx.latencyClass && ['interactive', 'background', 'batch'].includes(ctx.latencyClass)) ? ctx.latencyClass : ((route.constraints && route.constraints.latency_class) || 'interactive'), 'standard', { allowLimitedInteractive, allowFlexInteractive }) : false;
const privateOk = p ? !(!allowPrivateTraining && traits.isPrivateRequest(ctx) && traits.trainsOrRetains(p)) : false;
const privateOk = p ? !(!allowPrivateTraining && traits.isPrivateRequest(ctx) && traits.trainsOrRetains(p)) : false;
- const excluded = !policyOk ? 'content_policy'
+ const excluded = excludedModel ? 'model is explicitly excluded from routing'
+ : !policyOk ? 'content_policy'
: !offered ? (p && billingProfile(p) === 'byok' ? 'byok' : 'free_only')
: !offered ? (p && billingProfile(p) === 'byok' ? 'byok' : 'free_only')
: !access ? `provider_access ${modelCatalog.accessOf(p)}` : !latencyOk ? 'latency_class'
: !access ? `provider_access ${modelCatalog.accessOf(p)}` : !latencyOk ? 'latency_class'
: !privateOk ? 'private_data_training' : why;
: !privateOk ? 'private_data_training' : why;
explain.candidates.push({
explain.candidates.push({
- provider: c.provider, model: modelId, eligible: offered && access && policyOk && latencyOk && privateOk && !why,
+ provider: c.provider, model: modelId, eligible: !excludedModel && offered && access && policyOk && latencyOk && privateOk && !why,
excluded_reason: excluded, cost: null, latency: null,
excluded_reason: excluded, cost: null, latency: null,
});
});
- if (offered && access && policyOk && latencyOk && privateOk) order.push({ provider: c.provider, model: c.model || null });
+ if (!excludedModel && offered && access && policyOk && latencyOk && privateOk) order.push({ provider: c.provider, model: c.model || null });
}
}
return { order, explain };
return { order, explain };
}
}
@@ -710,10 +710,32 @@ const PROVIDERS = [
@@ -710,10 +710,32 @@ const PROVIDERS = [
],
],
};
};
}),
}),
- // Providers without a hand-kept model list: the owner syncs and enables their models.
- { key: 'meta', display_name: 'Meta Model API', base_url: 'https://api.meta.ai/v1',
- docs_url: 'https://dev.meta.ai/docs/api-reference', capabilities: ['chat', 'vision', 'tools', 'web-search'],
- search_adapters: ['meta'] },
+ // Meta Model API (prices/specs read from Meta's own pages on 2026-11-10). Contributor variants are
+ // intentionally absent: they permit Meta to train on prompts and must never enter the managed pool.
+ { key: 'meta', keys_url: 'https://dev.meta.ai/', display_name: 'Meta Model API', base_url: 'https://api.meta.ai/v1',
+ default_model: 'muse-spark-1.3', docs_url: 'https://dev.meta.ai/docs/api-reference',
+ capabilities: ['chat', 'vision', 'tools', 'json', 'web-search'], search_adapters: ['meta'],
+ traits: traits({ latency_classes: ['interactive', 'background', 'batch'], source: 'https://dev.meta.ai/docs/pricing-rate-limits', checked: '2026-11-10' }),
+ privacy: privacy({ label: 'anonymized', terms_url: 'https://dev.meta.ai/docs/pricing-rate-limits',
+ wording: 'Standard mode content is not used to train Meta models; Contributor mode is excluded.', checked: '2026-11-10' }),
+ data_policy: policy({ trains: false, retention: null, trust: 'first-party',
+ retention_note: 'Standard mode content is not used to train Meta models; retention is not stated on the pricing page.',
+ terms: 'https://dev.meta.ai/docs/pricing-rate-limits', checked: '2026-11-10' }),
+ models: [
+ { model_key: 'muse-ember-1.0', display_name: 'Muse Ember 1.0', family: 'muse-ember', context_window: 1048576, max_output: 524288,
+ cost: { in_per_mtok: 0.10, cached_per_mtok: 0.004, out_per_mtok: 0.50 },
+ source: 'https://dev.meta.ai/docs/pricing-rate-limits', checked: '2026-11-10',
+ supports: { json: true, tools: true, streaming: true, vision: true },
+ quantization: null, refusal_rate: null,
+ note: 'Standard mode only; reasoning effort minimal to max (default max). Requests above 200K input tokens bill the entire request at 3x: $0.30 input, $0.012 cached input, $1.50 output per 1M.' },
+ { model_key: 'muse-spark-1.3', display_name: 'Muse Spark 1.3', family: 'muse-spark', context_window: 1048576,
+ cost: { in_per_mtok: 1.25, cached_per_mtok: 0.15, out_per_mtok: 4.25 },
+ source: 'https://dev.meta.ai/docs/pricing-rate-limits', checked: '2026-11-10',
+ supports: { json: true, tools: true, streaming: true, vision: true },
+ quantization: null, refusal_rate: null,
+ note: 'Standard mode only; reasoning effort minimal to max. Contributor mode is excluded.' },
+ ],
+ },
{ key: 'deepseek', keys_url: 'https://platform.deepseek.com/api_keys', display_name: 'DeepSeek', base_url: 'https://api.deepseek.com/v1', default_model: 'deepseek-flash',
{ key: 'deepseek', keys_url: 'https://platform.deepseek.com/api_keys', display_name: 'DeepSeek', base_url: 'https://api.deepseek.com/v1', default_model: 'deepseek-flash',
docs_url: 'https://api-docs.deepseek.com/', capabilities: ['chat', 'vision', 'tools', 'web-search'], search_adapters: ['deepseek'] },
docs_url: 'https://api-docs.deepseek.com/', capabilities: ['chat', 'vision', 'tools', 'web-search'], search_adapters: ['deepseek'] },
{ key: 'anthropic', keys_url: 'https://console.anthropic.com/settings/keys', display_name: 'Anthropic', kind: 'anthropic', base_url: 'https://api.anthropic.com/v1', auth_mode: 'x-api-key',
{ key: 'anthropic', keys_url: 'https://console.anthropic.com/settings/keys', display_name: 'Anthropic', kind: 'anthropic', base_url: 'https://api.anthropic.com/v1', auth_mode: 'x-api-key',
@@ -985,4 +1007,9 @@ function isUncensored(provider, model) {
@@ -985,4 +1007,9 @@ function isUncensored(provider, model) {
return /abliterated|uncensored/i.test(id);
return /abliterated|uncensored/i.test(id);
}
}
-module.exports = { PROVIDERS, PROVIDER_KEYS, CHECKED, byKey, priceOf, isUncensored, leaf };
+/** Contributor variants let Meta train on prompts and must never enter OpenVibe routing. */
+function isRoutingExcludedModel(modelKey, model = null) {
+ return /-contributor$/i.test(String(modelKey || '')) || Boolean(model && model.metadata && model.metadata.routing_excluded === true);
+}
+
+module.exports = { PROVIDERS, PROVIDER_KEYS, CHECKED, byKey, priceOf, isUncensored, isRoutingExcludedModel, leaf };
@@ -36,7 +36,7 @@ const DOMAIN = {
@@ -36,7 +36,7 @@ const DOMAIN = {
baseten: 'baseten.co', hyperbolic: 'hyperbolic.xyz', parasail: 'parasail.io', 'particle-ai': 'particle.ai',
baseten: 'baseten.co', hyperbolic: 'hyperbolic.xyz', parasail: 'parasail.io', 'particle-ai': 'particle.ai',
'inference-net': 'inference.net', 'atlas-cloud': 'atlascloud.ai', avian: 'avian.io',
'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',
nanogpt: 'nano-gpt.com', infermatic: 'totalgpt.ai', arliai: 'arliai.com', friendli: 'friendli.ai', scaleway: 'scaleway.com',
- iteracompute: 'iteracompute.com', sable: 'sableapi.com', inception: 'inceptionlabs.ai', dosai: 'dos.ai', 'runpod-vllm': 'runpod.io',
+ iteracompute: 'iteracompute.com', sable: 'sableapi.com', inception: 'inceptionlabs.ai', dosai: 'dos.ai', 'runpod-vllm': 'runpod.io', meta: 'meta.ai',
};
};
const onItsOwnSite = (url, key) => {
const onItsOwnSite = (url, key) => {
const host = new URL(url).hostname;
const host = new URL(url).hostname;
new file mode 100644
new file mode 100644
@@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
+'use strict';
+const assert = require('assert');
+const { PROVIDERS, isRoutingExcludedModel } = require('../server/providers/pool-providers');
+const seed = require('../server/providers/pool-seed');
+const { describe } = require('../server/providers/catalog');
+const { checkProvider } = require('../server/providers/check');
+const { boot, suite, seamServer } = require('./helpers');
+const t = suite('provider-muse');
+
+const meta = PROVIDERS.find((p) => p.key === 'meta');
+const model = (key) => meta.models.find((m) => m.model_key === key);
+
+t.test('Meta Muse records are dated, standard mode, priced, capable, and seeded off', () => {
+ assert.ok(meta);
+ const p = seed.recordOf(meta);
+ assert.strictEqual(p.status, 'disabled');
+ assert.strictEqual(p.default_model, 'muse-spark-1.3');
+ assert.strictEqual(p.metadata.data_policy.trains, false);
+ assert.deepStrictEqual(meta.models.map((m) => m.model_key), ['muse-ember-1.0', 'muse-spark-1.3']);
+ const ember = model('muse-ember-1.0');
+ assert.deepStrictEqual(ember.cost, { in_per_mtok: 0.10, cached_per_mtok: 0.004, out_per_mtok: 0.50 });
+ assert.match(ember.note, /above 200K input tokens bill the entire request at 3x/);
+ assert.strictEqual(ember.checked, '2026-11-10');
+ assert.strictEqual(ember.context_window, 1048576);
+ assert.strictEqual(ember.max_output, 524288);
+ assert.deepStrictEqual(ember.supports, { json: true, tools: true, streaming: true, vision: true });
+ assert.deepStrictEqual(model('muse-spark-1.3').cost, { in_per_mtok: 1.25, cached_per_mtok: 0.15, out_per_mtok: 4.25 });
+ assert.strictEqual(model('muse-spark-1.3').checked, '2026-11-10');
+ assert.strictEqual(describe('muse-ember-1.0').tier, 'fast');
+ assert.strictEqual(describe('muse-ember-1.0').reasoning, true);
+ assert.strictEqual(describe('muse-spark-1.3').tier, 'strong');
+ assert.strictEqual(describe('muse-spark-1.3').tools, true);
+ assert.ok(!meta.models.some((m) => /wonder-full|contributor/i.test(m.model_key)));
+});
+
+t.test('Contributor model variants are excluded even if manually activated', () => {
+ assert.strictEqual(isRoutingExcludedModel('muse-spark-1.3-contributor'), true);
+ assert.strictEqual(isRoutingExcludedModel('muse-spark-1.3', { metadata: { routing_excluded: true } }), true);
+ assert.strictEqual(isRoutingExcludedModel('muse-spark-1.3'), false);
+});
+
+t.test('Exchange catalog carries Muse prices and an explicit off state before owner enablement', async () => {
+ const { boot } = require('./helpers');
+ const host = await boot({ env: { AI_STUB_FALLBACK: 'false' } });
+ try {
+ await host.registry.upsertProvider({ ...seed.recordOf(meta), auth_mode: 'bearer', status: 'disabled', secret_ref: 'env:META_CATALOG_KEY' });
+ for (const m of meta.models) await host.registry.upsertModel(seed.modelOf(meta, m));
+ const env = { META_CATALOG_KEY: 'synthetic-meta-catalog-key' };
+ const catalog = require('../server/exchange/catalog').createCatalog({ db: host.db, registry: host.registry, pool: host.pool, env, config: host.config });
+ const out = await catalog.models();
+ for (const [id, expected] of [['muse-ember-1.0', 0.10], ['muse-spark-1.3', 1.25]]) {
+ const entry = out.models.find((m) => m.slug === `meta/${id}`);
+ assert.ok(entry, `${id} is listed`);
+ assert.strictEqual(entry.offers[0].price.in, expected);
+ assert.strictEqual(entry.offers[0].status, 'down');
+ assert.strictEqual(entry.offers[0].status_reason, 'off until the owner turns it on');
+ }
+ const under = await host.pool.costOf('meta', 'muse-ember-1.0', { input: 200000, cached: 100000, output: 100000 });
+ const over = await host.pool.costOf('meta', 'muse-ember-1.0', { input: 200001, cached: 100000, output: 100000 });
+ assert.ok(Math.abs(under - 0.0604) < 1e-10, `200K uses standard rates across uncached, cached and output tokens: ${under}`);
+ assert.ok(Math.abs(over - 0.1812003) < 1e-10, `over 200K scales the entire request, not only excess tokens: ${over}`);
+ await host.registry.upsertProvider({ key: 'meta', status: 'active' });
+ const choices = require('../server/choices').createChoices({ db: host.db, registry: host.registry, catalog, ratings: async () => [] });
+ await choices.seed();
+ const chip = await choices.get('chat.reply');
+ const ember = chip.options.openvibe.filter((row) => row.model === 'meta/muse-ember-1.0');
+ const spark = chip.options.openvibe.filter((row) => row.model === 'meta/muse-spark-1.3');
+ assert.strictEqual(ember.length, 1, 'the shared chip receives Ember once through its live options feed');
+ assert.strictEqual(spark.length, 1, 'the shared chip receives Spark once through its live options feed');
+ } finally { await host.stop(); }
+});
+
+let h;
+let seam;
+t.test('router skips a manually activated Contributor model and uses the eligible fallback', async () => {
+ seam = await seamServer(() => ({ text: 'eligible answer', model: 'fallback-model', usage: { input: 2, output: 2 } }));
+ h = await boot({ env: { AI_STUB_FALLBACK: 'false' } });
+ const addProvider = (key) => h.registry.upsertProvider({ key, kind: 'http', auth_mode: 'none', status: 'active', base_url: seam.url, capabilities: ['chat'], priority: 50 });
+ const addModel = (provider_key, model_key, metadata = {}) => h.registry.upsertModel({ provider_key, model_key, status: 'active', cost: { in_per_mtok: 0.01, out_per_mtok: 0.01 }, metadata, supports: { streaming: true } });
+ await addProvider('meta');
+ await addModel('meta', 'muse-spark-1.3-contributor');
+ await addProvider('fallback-open');
+ await addModel('fallback-open', 'fallback-model');
+ const route = { key: 'muse-contributor', primary: { provider: 'meta', model: 'muse-spark-1.3-contributor' }, fallbacks: [{ provider: 'fallback-open', model: 'fallback-model' }] };
+ const result = await h.pool.execute(route, 'chat', { messages: [{ role: 'user', content: 'hello' }], maxTokens: 4 });
+ assert.strictEqual(result.provider, 'fallback-open');
+ assert.ok(result.explain.candidates.some((c) => c.provider === 'meta' && /excluded from routing/.test(c.excluded_reason || '')));
+});
+
+t.test('the owner check path sends one tiny model-list read and never exposes credentials', async () => {
+ let count = 0;
+ const result = await checkProvider({ key: 'meta', auth_mode: 'bearer', secret_ref: 'env:META_TEST_KEY', base_url: 'https://api.meta.ai/v1', kind: 'openai' }, {
+ env: { META_TEST_KEY: 'synthetic-meta-key-not-a-real-secret' },
+ fetchImpl: async (url, init) => {
+ count++;
+ assert.strictEqual(String(url), 'https://api.meta.ai/v1/models');
+ assert.strictEqual(init.method, 'GET');
+ assert.strictEqual(init.headers.Authorization, 'Bearer synthetic-meta-key-not-a-real-secret');
+ return new Response(JSON.stringify({ data: [{ id: 'muse-ember-1.0' }] }), { status: 200, headers: { 'content-type': 'application/json' } });
+ },
+ });
+ assert.strictEqual(count, 1);
+ assert.strictEqual(result.ok, true);
+ assert.strictEqual(result.requests, 1);
+ assert.ok(!JSON.stringify(result).includes('synthetic-meta-key'));
+});
+
+t.run();