openvibers/openvibe.ai Commits

Add Meta Muse models with owner-off routing (#458)

OP OpenVibers committed 2 days ago · 38311c505f · 6 files, +168 −17

* Add Meta Muse models with owner-off routing

* Exclude Meta Contributor variants from pinned routes

* Apply Muse Ember long-context billing multiplier

Changes

ddiff --git a/server/exchange/catalog.js b/server/exchange/catalog.js
iindex a9f4703..703278d 100644
---- a/server/exchange/catalog.js
++++ b/server/exchange/catalog.js
@@@ -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.
ddiff --git a/server/providers/catalog.js b/server/providers/catalog.js
iindex 183dbde..c7fbda1 100644
---- a/server/providers/catalog.js
++++ b/server/providers/catalog.js
@@@ -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 });
ddiff --git a/server/providers/index.js b/server/providers/index.js
iindex 0794d6b..0d65ab4 100644
---- a/server/providers/index.js
++++ b/server/providers/index.js
@@@ -33,6 +33,7 @@ const { retryable, sleep, ProviderError, schemaInstruction, insufficientBalance
const { 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.no
async 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.no
const 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 };
}
ddiff --git a/server/providers/pool-providers.js b/server/providers/pool-providers.js
iindex 858988b..ca74433 100644
---- a/server/providers/pool-providers.js
++++ b/server/providers/pool-providers.js
@@@ -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 };
ddiff --git a/test/ai-provider-adapters.test.js b/test/ai-provider-adapters.test.js
iindex 121a2ef..433a680 100644
---- a/test/ai-provider-adapters.test.js
++++ b/test/ai-provider-adapters.test.js
@@@ -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;
ddiff --git a/test/provider-muse.test.js b/test/provider-muse.test.js
nnew file mode 100644
iindex 0000000..a42d2d7
---- /dev/null
++++ b/test/provider-muse.test.js
@@@ -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();