'use strict'; /** * Mistral AI pricing fetcher. * * As of 2026 mistral.ai re-platformed onto Astro and moved API pricing to * /pricing/api/ (the old /pricing now shows Le Chat subscription plans, and the * previous Next.js RSC "apis" payload is gone). On the new page each model is a * card: * *
Mistral Medium 3.5
* ... *Input (/M tokens)
Output (/M tokens)
that shares its row (its parent's first
). const acc = new Map(); let currentName = null; $('p.text-h5.font-mistral, mistral-atom-text-price').each((_, el) => { if (el.tagName === 'p') { currentName = $(el).text().trim(); return; } if (!currentName) return; const label = $(el).parent().find('p').first().text().trim().toLowerCase(); const val = priceUsd($, el); if (val === null) return; const m = acc.get(currentName) || { name: currentName }; if (label.startsWith('input') && label.includes('m tokens')) m.input = val; else if (label.startsWith('output') && label.includes('m tokens')) m.output = val; else if (label.includes('min')) m.perMin = val; acc.set(currentName, m); }); const models = []; for (const m of acc.values()) { // Only emit rows we could price as a model (token or per-minute). This drops // Agent-API feature rows (per-call / per-1K-images) and free entries. if (m.input == null && m.output == null && m.perMin == null) continue; const type = getModelType(m.name); const caps = []; if (type === 'audio') caps.push('audio'); if (/magistral/i.test(m.name)) caps.push('reasoning'); const model = { name: m.name, type, currency: 'USD' }; if (caps.length) model.capabilities = caps; if (m.perMin != null) model.price_per_minute = m.perMin; if (m.input != null) model.input_price_per_1m = m.input; if (m.output != null) model.output_price_per_1m = m.output; // Chat/embedding models should always expose an output field for the UI. if (type !== 'audio' && model.input_price_per_1m != null && model.output_price_per_1m == null) { model.output_price_per_1m = 0; } const size_b = getSizeB(m.name); if (size_b) model.size_b = size_b; models.push(model); } if (models.length === 0) { throw new Error('No priced models parsed from Mistral pricing page (structure changed?)'); } return models; } module.exports = { fetchMistral, providerName: 'Mistral AI' }; // Run standalone: node scripts/providers/mistral.js if (require.main === module) { fetchMistral() .then((models) => { console.log(`Fetched ${models.length} models from Mistral AI:\n`); const byType = {}; models.forEach((m) => { (byType[m.type] = byType[m.type] || []).push(m); }); for (const [type, ms] of Object.entries(byType)) { console.log(` [${type}]`); ms.forEach((m) => { let priceStr = ''; if (m.price_per_minute !== undefined) priceStr += `$${m.price_per_minute}/min `; if (m.input_price_per_1m !== undefined || m.output_price_per_1m !== undefined) { priceStr += `$${m.input_price_per_1m ?? 0} / $${m.output_price_per_1m ?? 0}`; } console.log(` ${m.name.padEnd(40)} ${priceStr.trim()}`); }); } }) .catch((err) => { console.error('Error:', err.message); process.exit(1); }); }