openvibers/openvibe.ai Commits
Gemini 4 Hydrogen in the catalogue, with the free tier's real facts (#520)
Changes
@@@ -231,7 +231,7 @@ const FREE_TIERS = {},gemini: {available: true,-- summary: 'Free tier gives free input and output tokens on supported models (gemini-3.8-flash, gemini-3.6-flash, gemini-3.5-flash-lite were free on the pricing page). Gemini 4 Carbon/Argon/Hydrogen are not available on the Free tier.',++ summary: 'Free tier: input, output and context caching are free of charge on the models the pricing page marks free, Gemini 4 Hydrogen among them (read 2026-11-12); Search and Maps grounding are not part of it. The rate-limits page prints no free-tier row for Hydrogen, so its free limits are not stated; limits are per project, requests per day reset at midnight Pacific, and a project sees its own at aistudio.google.com/rate-limit.',limits: { rpm: null, rpd: null, tpm: null, tpd: null },card_required: false,trains_on_free: true,@@@ -891,7 +891,30 @@ const PROVIDERS = [{ key: 'gemini', models_dev_id: 'google', keys_url: 'https://aistudio.google.com/apikey', display_name: 'Google AI Studio (Gemini API)', base_url: 'https://generativelanguage.googleapis.com/v1beta/openai',models_endpoint: 'https://generativelanguage.googleapis.com/v1beta/models',api_family: 'google', docs_url: 'https://ai.google.dev/gemini-api/docs/openai', capabilities: ['chat', 'vision', 'tools', 'embed'],-- free_tier: FREE_TIERS.gemini },++ free_tier: FREE_TIERS.gemini,++ // The key the network asks for is a free one, and Google uses the free tier's prompts and answers to improve++ // its products: the label and the policy are the free tier's, so a run that asks for privacy never lands here++ // and only public or synthetic work (Bench, public repos) does.++ privacy: privacy({++ label: 'anonymized', terms_url: 'https://ai.google.dev/gemini-api/terms',++ wording: FREE_TIERS.gemini.wording, checked: '2026-11-12',++ }),++ data_policy: policy({++ trains: true, retention: null, trust: 'first-party', region: null,++ retention_note: 'unpaid quota: Google uses the prompts and answers to improve its products and machine learning',++ terms: 'https://ai.google.dev/gemini-api/terms', checked: '2026-11-12',++ }),++ // Gemini's Batch and Flex tiers are half the list price, but no multiplier is recorded: the Gemini batch adapter++ // stays disabled until its endpoint is read (server/batch/providers/gemini.js), and the network never offers a++ // tier it cannot run. Priority (1.8x) is above the list price, which no tier here is.++ models: [{++ model_key: 'gemini-4-hydrogen', display_name: 'Gemini 4 Hydrogen', family: 'gemini', context_window: 1048576,++ cost: { in_per_mtok: 1.50, cached_per_mtok: 0.075, out_per_mtok: 7.50 },++ source: 'https://ai.google.dev/gemini-api/docs/pricing', checked: '2026-11-12',++ supports: { json: true, tools: true, streaming: true, vision: false },++ quantization: null, refusal_rate: null,++ note: 'Everyday coding and agentic work, 1M context and long answers. Free tier: input, output and caching free, and Google uses the free tier\'s prompts and answers to improve its products, so public or synthetic work only. Paid: caching is implicit only; Batch and Flex $0.75 in / $3.75 out; Priority $2.70 / $13.50 per 1M.' }],++ },{ key: 'xai', keys_url: 'https://console.x.ai/', display_name: 'xAI', base_url: 'https://api.x.ai/v1',docs_url: 'https://docs.x.ai/docs/api-reference', capabilities: ['chat', 'vision', 'tools', 'image', 'video'] },{ key: 'mistral', keys_url: 'https://console.mistral.ai/api-keys', display_name: 'Mistral', base_url: 'https://api.mistral.ai/v1',@@@ -118,6 +118,8 @@ async function seedPoolProviders({ registry, actor = 'system', env = process.envconst metadata = { ...defaults.metadata, ...prev.metadata, search_adapters: [...new Set([...(prev.metadata.search_adapters || []), ...(defaults.metadata.search_adapters || [])])] };if (p.key === 'gemini' && prev.base_url === p.base_url && prev.metadata.models_endpoint === `${p.base_url}/models`)metadata.models_endpoint = defaults.metadata.models_endpoint;++ // The free tier is a dated reading of the provider's own page: a newer reading replaces the one the row kept.++ if (defaults.metadata.free_tier) metadata.free_tier = defaults.metadata.free_tier;if (!configured && prev.base_url !== p.base_url) {metadata.models_endpoint = defaults.metadata.models_endpoint;metadata.api_family = defaults.metadata.api_family;@@@ -142,6 +142,8 @@ const STEPS = Object.freeze([{ why: 'cheapest that looks good' }),alternatives: [offer('altare', 'glm-5.3', 'GLM-5.3 on Altare', tokenPrice(1.4, 4.4, 0.26), SOURCES.altare, { why: 'free until December 1', note: ALTARE_NOTE }),++ offer('gemini', 'gemini-4-hydrogen', 'Gemini 4 Hydrogen', tokenPrice(1.5, 7.5, 0.075), SOURCES.gemini, { why: 'free tier, 1M context',++ note: 'Free on a free AI Studio key, but Google uses the free tier\'s prompts and answers to improve its products: public or synthetic work only (Bench, public repos), never a person\'s code. Paid is $1.50 in / $7.50 out per 1M.' }),offer('openai', 'gpt-5-mini', 'GPT-5 mini', tokenPrice(0.25, 2), SOURCES.openaiImages),offer('anthropic', 'claude-opus-5-5', 'Claude Opus 5.5', tokenPrice(4, 20, 0.2), 'https://platform.claude.com/docs/en/models/opus-5-5/overview'),],@@@ -137,6 +137,14 @@ t.test('every free tier states availability, its own page and the day it was reaassert.strictEqual(p.free_tier.checked, '2026-11-12', `${p.key} free tier checked`);}assert.strictEqual(pool.byKey.get('groq').free_tier.limits.rpm, 30, 'Groq free plan is 30 RPM');++ // Gemini 4 Hydrogen (2026-11-12): free on a free key, but Google trains on the free tier, so the policy says it trains++ // and the label is never private: a run that asks for privacy never lands on it.++ const hydrogen = pool.priceOf('gemini', 'gemini-4-hydrogen');++ assert.deepStrictEqual([hydrogen.cost.in_per_mtok, hydrogen.cost.cached_per_mtok, hydrogen.cost.out_per_mtok], [1.50, 0.075, 7.50]);++ assert.strictEqual(pool.byKey.get('gemini').free_tier.trains_on_free, true);++ assert.match(pool.byKey.get('gemini').free_tier.summary, /Hydrogen among them/);++ assert.strictEqual(pool.byKey.get('gemini').data_policy.trains, true);++ assert.strictEqual(privacy.labelFor({ record: { metadata: { data_policy: pool.byKey.get('gemini').data_policy } } }).label, 'anonymized');});t.test('the seed writes the privacy label, the quantization and the refusal rate into the registry', async () => {@@@ -162,6 +170,11 @@ t.test('the seed writes the privacy label, the quantization and the refusal rateassert.strictEqual(models.get('refuseless/deepseek-v4.1-flash-abliterated').cost.in_per_mtok, null);assert.strictEqual(models.get('abliteration/abliterated-model').cost.in_per_mtok, 1.00);assert.strictEqual(models.get('abliteration/abliterated-model').metadata.privacy.label, 'private');++ // A newer reading of a free tier replaces the one a catalogue row kept.++ saved.set('gemini', { ...saved.get('gemini'), origin: 'catalog', metadata: { ...saved.get('gemini').metadata, free_tier: { summary: 'an older reading', checked: '2026-11-01' } } });++ await seed.seedPoolProviders({ registry });++ assert.strictEqual(saved.get('gemini').metadata.free_tier.checked, '2026-11-12', 'the catalogue\'s newer free tier wins');++ assert.strictEqual(models.get('gemini/gemini-4-hydrogen').cost.in_per_mtok, 1.50);// The owner edits a provider: a second seed never touches its row.saved.set('qwen', { ...saved.get('qwen'), origin: 'admin', base_url: 'https://proxy.example/v1' });const again = await seed.seedPoolProviders({ registry });
@@ -231,7 +231,7 @@ const FREE_TIERS = {
@@ -231,7 +231,7 @@ const FREE_TIERS = {
},
},
gemini: {
gemini: {
available: true,
available: true,
- summary: 'Free tier gives free input and output tokens on supported models (gemini-3.8-flash, gemini-3.6-flash, gemini-3.5-flash-lite were free on the pricing page). Gemini 4 Carbon/Argon/Hydrogen are not available on the Free tier.',
+ summary: 'Free tier: input, output and context caching are free of charge on the models the pricing page marks free, Gemini 4 Hydrogen among them (read 2026-11-12); Search and Maps grounding are not part of it. The rate-limits page prints no free-tier row for Hydrogen, so its free limits are not stated; limits are per project, requests per day reset at midnight Pacific, and a project sees its own at aistudio.google.com/rate-limit.',
limits: { rpm: null, rpd: null, tpm: null, tpd: null },
limits: { rpm: null, rpd: null, tpm: null, tpd: null },
card_required: false,
card_required: false,
trains_on_free: true,
trains_on_free: true,
@@ -891,7 +891,30 @@ const PROVIDERS = [
@@ -891,7 +891,30 @@ const PROVIDERS = [
{ key: 'gemini', models_dev_id: 'google', keys_url: 'https://aistudio.google.com/apikey', display_name: 'Google AI Studio (Gemini API)', base_url: 'https://generativelanguage.googleapis.com/v1beta/openai',
{ key: 'gemini', models_dev_id: 'google', keys_url: 'https://aistudio.google.com/apikey', display_name: 'Google AI Studio (Gemini API)', base_url: 'https://generativelanguage.googleapis.com/v1beta/openai',
models_endpoint: 'https://generativelanguage.googleapis.com/v1beta/models',
models_endpoint: 'https://generativelanguage.googleapis.com/v1beta/models',
api_family: 'google', docs_url: 'https://ai.google.dev/gemini-api/docs/openai', capabilities: ['chat', 'vision', 'tools', 'embed'],
api_family: 'google', docs_url: 'https://ai.google.dev/gemini-api/docs/openai', capabilities: ['chat', 'vision', 'tools', 'embed'],
- free_tier: FREE_TIERS.gemini },
+ free_tier: FREE_TIERS.gemini,
+ // The key the network asks for is a free one, and Google uses the free tier's prompts and answers to improve
+ // its products: the label and the policy are the free tier's, so a run that asks for privacy never lands here
+ // and only public or synthetic work (Bench, public repos) does.
+ privacy: privacy({
+ label: 'anonymized', terms_url: 'https://ai.google.dev/gemini-api/terms',
+ wording: FREE_TIERS.gemini.wording, checked: '2026-11-12',
+ }),
+ data_policy: policy({
+ trains: true, retention: null, trust: 'first-party', region: null,
+ retention_note: 'unpaid quota: Google uses the prompts and answers to improve its products and machine learning',
+ terms: 'https://ai.google.dev/gemini-api/terms', checked: '2026-11-12',
+ }),
+ // Gemini's Batch and Flex tiers are half the list price, but no multiplier is recorded: the Gemini batch adapter
+ // stays disabled until its endpoint is read (server/batch/providers/gemini.js), and the network never offers a
+ // tier it cannot run. Priority (1.8x) is above the list price, which no tier here is.
+ models: [{
+ model_key: 'gemini-4-hydrogen', display_name: 'Gemini 4 Hydrogen', family: 'gemini', context_window: 1048576,
+ cost: { in_per_mtok: 1.50, cached_per_mtok: 0.075, out_per_mtok: 7.50 },
+ source: 'https://ai.google.dev/gemini-api/docs/pricing', checked: '2026-11-12',
+ supports: { json: true, tools: true, streaming: true, vision: false },
+ quantization: null, refusal_rate: null,
+ note: 'Everyday coding and agentic work, 1M context and long answers. Free tier: input, output and caching free, and Google uses the free tier\'s prompts and answers to improve its products, so public or synthetic work only. Paid: caching is implicit only; Batch and Flex $0.75 in / $3.75 out; Priority $2.70 / $13.50 per 1M.' }],
+ },
{ key: 'xai', keys_url: 'https://console.x.ai/', display_name: 'xAI', base_url: 'https://api.x.ai/v1',
{ key: 'xai', keys_url: 'https://console.x.ai/', display_name: 'xAI', base_url: 'https://api.x.ai/v1',
docs_url: 'https://docs.x.ai/docs/api-reference', capabilities: ['chat', 'vision', 'tools', 'image', 'video'] },
docs_url: 'https://docs.x.ai/docs/api-reference', capabilities: ['chat', 'vision', 'tools', 'image', 'video'] },
{ key: 'mistral', keys_url: 'https://console.mistral.ai/api-keys', display_name: 'Mistral', base_url: 'https://api.mistral.ai/v1',
{ key: 'mistral', keys_url: 'https://console.mistral.ai/api-keys', display_name: 'Mistral', base_url: 'https://api.mistral.ai/v1',
@@ -118,6 +118,8 @@ async function seedPoolProviders({ registry, actor = 'system', env = process.env
@@ -118,6 +118,8 @@ async function seedPoolProviders({ registry, actor = 'system', env = process.env
const metadata = { ...defaults.metadata, ...prev.metadata, search_adapters: [...new Set([...(prev.metadata.search_adapters || []), ...(defaults.metadata.search_adapters || [])])] };
const metadata = { ...defaults.metadata, ...prev.metadata, search_adapters: [...new Set([...(prev.metadata.search_adapters || []), ...(defaults.metadata.search_adapters || [])])] };
if (p.key === 'gemini' && prev.base_url === p.base_url && prev.metadata.models_endpoint === `${p.base_url}/models`)
if (p.key === 'gemini' && prev.base_url === p.base_url && prev.metadata.models_endpoint === `${p.base_url}/models`)
metadata.models_endpoint = defaults.metadata.models_endpoint;
metadata.models_endpoint = defaults.metadata.models_endpoint;
+ // The free tier is a dated reading of the provider's own page: a newer reading replaces the one the row kept.
+ if (defaults.metadata.free_tier) metadata.free_tier = defaults.metadata.free_tier;
if (!configured && prev.base_url !== p.base_url) {
if (!configured && prev.base_url !== p.base_url) {
metadata.models_endpoint = defaults.metadata.models_endpoint;
metadata.models_endpoint = defaults.metadata.models_endpoint;
metadata.api_family = defaults.metadata.api_family;
metadata.api_family = defaults.metadata.api_family;
@@ -142,6 +142,8 @@ const STEPS = Object.freeze([
@@ -142,6 +142,8 @@ const STEPS = Object.freeze([
{ why: 'cheapest that looks good' }),
{ why: 'cheapest that looks good' }),
alternatives: [
alternatives: [
offer('altare', 'glm-5.3', 'GLM-5.3 on Altare', tokenPrice(1.4, 4.4, 0.26), SOURCES.altare, { why: 'free until December 1', note: ALTARE_NOTE }),
offer('altare', 'glm-5.3', 'GLM-5.3 on Altare', tokenPrice(1.4, 4.4, 0.26), SOURCES.altare, { why: 'free until December 1', note: ALTARE_NOTE }),
+ offer('gemini', 'gemini-4-hydrogen', 'Gemini 4 Hydrogen', tokenPrice(1.5, 7.5, 0.075), SOURCES.gemini, { why: 'free tier, 1M context',
+ note: 'Free on a free AI Studio key, but Google uses the free tier\'s prompts and answers to improve its products: public or synthetic work only (Bench, public repos), never a person\'s code. Paid is $1.50 in / $7.50 out per 1M.' }),
offer('openai', 'gpt-5-mini', 'GPT-5 mini', tokenPrice(0.25, 2), SOURCES.openaiImages),
offer('openai', 'gpt-5-mini', 'GPT-5 mini', tokenPrice(0.25, 2), SOURCES.openaiImages),
offer('anthropic', 'claude-opus-5-5', 'Claude Opus 5.5', tokenPrice(4, 20, 0.2), 'https://platform.claude.com/docs/en/models/opus-5-5/overview'),
offer('anthropic', 'claude-opus-5-5', 'Claude Opus 5.5', tokenPrice(4, 20, 0.2), 'https://platform.claude.com/docs/en/models/opus-5-5/overview'),
],
],
@@ -137,6 +137,14 @@ t.test('every free tier states availability, its own page and the day it was rea
@@ -137,6 +137,14 @@ t.test('every free tier states availability, its own page and the day it was rea
assert.strictEqual(p.free_tier.checked, '2026-11-12', `${p.key} free tier checked`);
assert.strictEqual(p.free_tier.checked, '2026-11-12', `${p.key} free tier checked`);
}
}
assert.strictEqual(pool.byKey.get('groq').free_tier.limits.rpm, 30, 'Groq free plan is 30 RPM');
assert.strictEqual(pool.byKey.get('groq').free_tier.limits.rpm, 30, 'Groq free plan is 30 RPM');
+ // Gemini 4 Hydrogen (2026-11-12): free on a free key, but Google trains on the free tier, so the policy says it trains
+ // and the label is never private: a run that asks for privacy never lands on it.
+ const hydrogen = pool.priceOf('gemini', 'gemini-4-hydrogen');
+ assert.deepStrictEqual([hydrogen.cost.in_per_mtok, hydrogen.cost.cached_per_mtok, hydrogen.cost.out_per_mtok], [1.50, 0.075, 7.50]);
+ assert.strictEqual(pool.byKey.get('gemini').free_tier.trains_on_free, true);
+ assert.match(pool.byKey.get('gemini').free_tier.summary, /Hydrogen among them/);
+ assert.strictEqual(pool.byKey.get('gemini').data_policy.trains, true);
+ assert.strictEqual(privacy.labelFor({ record: { metadata: { data_policy: pool.byKey.get('gemini').data_policy } } }).label, 'anonymized');
});
});
t.test('the seed writes the privacy label, the quantization and the refusal rate into the registry', async () => {
t.test('the seed writes the privacy label, the quantization and the refusal rate into the registry', async () => {
@@ -162,6 +170,11 @@ t.test('the seed writes the privacy label, the quantization and the refusal rate
@@ -162,6 +170,11 @@ t.test('the seed writes the privacy label, the quantization and the refusal rate
assert.strictEqual(models.get('refuseless/deepseek-v4.1-flash-abliterated').cost.in_per_mtok, null);
assert.strictEqual(models.get('refuseless/deepseek-v4.1-flash-abliterated').cost.in_per_mtok, null);
assert.strictEqual(models.get('abliteration/abliterated-model').cost.in_per_mtok, 1.00);
assert.strictEqual(models.get('abliteration/abliterated-model').cost.in_per_mtok, 1.00);
assert.strictEqual(models.get('abliteration/abliterated-model').metadata.privacy.label, 'private');
assert.strictEqual(models.get('abliteration/abliterated-model').metadata.privacy.label, 'private');
+ // A newer reading of a free tier replaces the one a catalogue row kept.
+ saved.set('gemini', { ...saved.get('gemini'), origin: 'catalog', metadata: { ...saved.get('gemini').metadata, free_tier: { summary: 'an older reading', checked: '2026-11-01' } } });
+ await seed.seedPoolProviders({ registry });
+ assert.strictEqual(saved.get('gemini').metadata.free_tier.checked, '2026-11-12', 'the catalogue\'s newer free tier wins');
+ assert.strictEqual(models.get('gemini/gemini-4-hydrogen').cost.in_per_mtok, 1.50);
// The owner edits a provider: a second seed never touches its row.
// The owner edits a provider: a second seed never touches its row.
saved.set('qwen', { ...saved.get('qwen'), origin: 'admin', base_url: 'https://proxy.example/v1' });
saved.set('qwen', { ...saved.get('qwen'), origin: 'admin', base_url: 'https://proxy.example/v1' });
const again = await seed.seedPoolProviders({ registry });
const again = await seed.seedPoolProviders({ registry });