> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getbifrost.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# List of Supported Models

> Comprehensive catalog of supported AI models with detailed specifications, capabilities, and costs

export const formatColumnName = name => {
  let formatted = name.replace(/_/g, ' ');
  formatted = formatted.replace(/([A-Z])/g, ' $1');
  return formatted.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ').trim();
};

export const formatTokens = n => {
  if (n == null || !Number.isFinite(n)) return null;
  if (n >= 1000000) return (n % 1000000 === 0 ? n / 1000000 : (n / 1000000).toFixed(1)) + 'M';
  if (n >= 1000) return Math.round(n / 1000) + 'K';
  return String(n);
};

export const formatCostPerMillion = c => {
  if (c == null || !Number.isFinite(c)) return null;
  const per = c * 1000000;
  if (per === 0) return 'Free';
  if (per >= 100) return '$' + per.toFixed(0);
  if (per >= 0.01) return '$' + per.toFixed(2);
  return '$' + per.toFixed(4);
};

export const CopyIcon = ({done}) => done ? <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
    <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
  </svg> : <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
    <path strokeLinecap="round" strokeLinejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
  </svg>;

export const ModelDialog = ({model, onClose}) => {
  const [copied, setCopied] = useState(null);
  if (!model) return null;
  const modelId = model.provider ? model.provider + '/' + model.model : model.model;
  const jsonString = JSON.stringify(model, null, 2);
  const copy = (key, text) => {
    navigator.clipboard.writeText(text);
    setCopied(key);
    setTimeout(() => setCopied(null), 1500);
  };
  const capabilities = Object.keys(model).filter(k => k.startsWith('supports_') && model[k] === true).map(k => formatColumnName(k.slice(('supports_').length))).sort();
  const specs = [{
    label: 'Max output',
    value: formatTokens(model.max_output_tokens ?? model.max_tokens)
  }, {
    label: 'Input / 1M tokens',
    value: formatCostPerMillion(model.input_cost_per_token)
  }, {
    label: 'Output / 1M tokens',
    value: formatCostPerMillion(model.output_cost_per_token)
  }].filter(s => s.value != null);
  return <dialog ref={node => {
    if (node && !node.open) node.showModal();
  }} onClose={onClose} onClick={e => {
    if (e.target === e.currentTarget) onClose();
  }} className="model-dialog-panel m-auto p-0 bg-white dark:bg-zinc-900 rounded-2xl shadow-2xl ring-1 ring-zinc-950/10 dark:ring-white/10 max-w-2xl w-[calc(100%-2rem)] max-h-[85vh] overflow-hidden">
      <div className="flex flex-col max-h-[85vh]">
        <div className="flex items-start gap-3 p-5 border-b border-zinc-950/10 dark:border-white/10">
          <div className="shrink-0 w-10 h-10 rounded-lg flex items-center justify-center font-semibold uppercase bg-[#0C3B43]/10 text-[#0C3B43] dark:bg-[#07C983]/10 dark:text-[#07C983]">
            {(model.provider || model.model || '?').charAt(0)}
          </div>
          <div className="min-w-0 flex-1">
            <h3 className="font-mono text-base font-semibold text-zinc-950 dark:text-white break-all leading-snug">
              {model.model || 'Unknown'}
            </h3>
            <div className="mt-0.5 text-sm text-zinc-950/60 dark:text-white/60">
              <span className="capitalize">{model.provider || 'unknown provider'}</span>
              {model.mode && <span> · {formatColumnName(model.mode)}</span>}
            </div>
          </div>
          <button onClick={onClose} className="shrink-0 p-2 -m-1 text-zinc-950/60 dark:text-white/60 hover:text-zinc-950 dark:hover:text-white hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg transition-colors" aria-label="Close">
            <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
            </svg>
          </button>
        </div>

        <div className="flex-1 overflow-auto p-5 space-y-5">
          <div>
            <div className="text-[11px] font-semibold uppercase tracking-widest text-zinc-950/50 dark:text-white/50 mb-2">
              Model ID — use in any Bifrost request
            </div>
            <div className="flex items-center gap-2 bg-zinc-50 dark:bg-zinc-950 pl-4 pr-2 py-2 rounded-xl border border-zinc-950/10 dark:border-white/10">
              <code className="flex-1 min-w-0 bg-transparent font-mono text-sm text-zinc-950 dark:text-white break-all">
                {modelId}
              </code>
              <button onClick={() => copy('id', modelId)} className="shrink-0 p-2 rounded-lg text-zinc-950/50 dark:text-white/50 hover:text-zinc-950 dark:hover:text-white hover:bg-zinc-950/5 dark:hover:bg-white/10 transition-colors" aria-label="Copy model ID">
                <CopyIcon done={copied === 'id'} />
              </button>
            </div>
          </div>

          {specs.length > 0 && <div className="grid grid-cols-2 sm:grid-cols-3 gap-px rounded-xl overflow-hidden border border-zinc-950/10 dark:border-white/10">
              {specs.map(spec => <div key={spec.label} className="bg-white dark:bg-zinc-900 px-3 py-2.5">
                  <div className="text-[10px] font-semibold uppercase tracking-wider text-zinc-950/50 dark:text-white/50">
                    {spec.label}
                  </div>
                  <div className="mt-0.5 text-sm font-semibold text-zinc-950 dark:text-white tabular-nums">
                    {spec.value}
                  </div>
                </div>)}
            </div>}

          {capabilities.length > 0 && <div>
              <div className="text-[11px] font-semibold uppercase tracking-widest text-zinc-950/50 dark:text-white/50 mb-2">
                Capabilities
              </div>
              <div className="flex flex-wrap gap-1.5">
                {capabilities.map(cap => <span key={cap} className="inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300">
                    {cap}
                  </span>)}
              </div>
            </div>}

          <div>
            <div className="flex items-center justify-between mb-2">
              <span className="text-[11px] font-semibold uppercase tracking-widest text-zinc-950/50 dark:text-white/50">
                Raw configuration
              </span>
              <button onClick={() => copy('json', jsonString)} className="inline-flex items-center gap-1.5 text-xs font-medium text-zinc-950/60 dark:text-white/60 hover:text-zinc-950 dark:hover:text-white transition-colors">
                <CopyIcon done={copied === 'json'} />
                {copied === 'json' ? 'Copied' : 'Copy JSON'}
              </button>
            </div>
            <pre className="text-xs leading-relaxed text-zinc-950/80 dark:text-white/80 bg-zinc-50 dark:bg-zinc-950 p-4 rounded-xl overflow-auto max-h-56 border border-zinc-950/10 dark:border-white/10">
              {jsonString}
            </pre>
          </div>
        </div>
      </div>
    </dialog>;
};

export const ModelsCatalog = () => {
  const [models, setModels] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [searchTerm, setSearchTerm] = useState('');
  const [selectedProvider, setSelectedProvider] = useState('all');
  const [selectedMode, setSelectedMode] = useState('all');
  const [selectedModel, setSelectedModel] = useState(null);
  const [visibleCount, setVisibleCount] = useState(100);
  useEffect(() => {
    async function fetchModels() {
      try {
        const response = await fetch('https://getbifrost.ai/datasheet');
        if (!response.ok) {
          throw new Error('Failed to fetch models: ' + response.status);
        }
        const data = await response.json();
        if (data && typeof data === 'object' && !Array.isArray(data)) {
          const modelsArray = Object.entries(data).map(([modelName, config]) => ({
            model: modelName,
            ...config
          }));
          if (modelsArray.length === 0) {
            throw new Error('No models data received');
          }
          setModels(modelsArray);
        } else if (Array.isArray(data)) {
          setModels(data);
        } else {
          throw new Error('Invalid data format');
        }
      } catch (err) {
        console.error('Fetch error:', err);
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }
    fetchModels();
  }, []);
  useEffect(() => {
    const styleId = 'models-catalog-styles';
    if (document.getElementById(styleId)) return;
    const style = document.createElement('style');
    style.id = styleId;
    style.textContent = `
      .catalog-scroll {
        scrollbar-width: thin;
        scrollbar-color: rgba(161, 161, 170, 0.5) transparent;
      }
      .catalog-scroll::-webkit-scrollbar {
        width: 8px;
        height: 8px;
      }
      .catalog-scroll::-webkit-scrollbar-track {
        background: transparent;
      }
      .catalog-scroll::-webkit-scrollbar-thumb {
        background-color: rgba(161, 161, 170, 0.5);
        border-radius: 8px;
      }
      .catalog-scroll::-webkit-scrollbar-thumb:hover {
        background-color: rgba(161, 161, 170, 0.8);
      }
      .model-dialog-panel::backdrop {
        background: rgba(9, 9, 11, 0.6);
        backdrop-filter: blur(4px);
      }
      @media (prefers-reduced-motion: no-preference) {
        .model-dialog-panel::backdrop {
          animation: model-dialog-fade 150ms ease-out;
        }
        .model-dialog-panel {
          animation: model-dialog-rise 180ms cubic-bezier(0.16, 1, 0.3, 1);
        }
      }
      @keyframes model-dialog-fade {
        from { opacity: 0; }
        to { opacity: 1; }
      }
      @keyframes model-dialog-rise {
        from { opacity: 0; transform: translateY(8px) scale(0.98); }
        to { opacity: 1; transform: translateY(0) scale(1); }
      }
    `;
    document.head.appendChild(style);
    return () => {
      const existingStyle = document.getElementById(styleId);
      if (existingStyle) {
        existingStyle.remove();
      }
    };
  }, []);
  useEffect(() => {
    if (!selectedModel) return;
    const onKey = e => {
      if (e.key === 'Escape') setSelectedModel(null);
    };
    document.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => {
      document.removeEventListener('keydown', onKey);
      document.body.style.overflow = '';
    };
  }, [selectedModel]);
  useEffect(() => {
    setVisibleCount(100);
  }, [searchTerm, selectedProvider, selectedMode]);
  const providers = useMemo(() => {
    const uniqueProviders = new Set();
    models.forEach(model => {
      if (model.provider) {
        uniqueProviders.add(model.provider);
      }
    });
    return Array.from(uniqueProviders).sort();
  }, [models]);
  const modes = useMemo(() => {
    const uniqueModes = new Set();
    models.forEach(model => {
      if (model.mode) {
        uniqueModes.add(model.mode);
      }
    });
    return Array.from(uniqueModes).sort();
  }, [models]);
  const filteredModels = useMemo(() => {
    let filtered = models;
    if (selectedProvider !== 'all') {
      filtered = filtered.filter(model => model.provider === selectedProvider);
    }
    if (selectedMode !== 'all') {
      filtered = filtered.filter(model => model.mode === selectedMode);
    }
    if (searchTerm) {
      const term = searchTerm.toLowerCase();
      filtered = filtered.filter(model => Object.values(model).some(value => String(value).toLowerCase().includes(term)));
    }
    return filtered;
  }, [models, searchTerm, selectedProvider, selectedMode]);
  const visibleModels = useMemo(() => filteredModels.slice(0, visibleCount), [filteredModels, visibleCount]);
  if (loading) {
    return <div className="w-full not-prose border border-zinc-950/10 dark:border-white/10 rounded-xl overflow-hidden">
        <div className="p-4 space-y-3">
          {[0, 1, 2, 3, 4, 5, 6, 7].map(i => <div key={i} className="flex items-center gap-4 animate-pulse">
              <div className="h-4 w-28 rounded bg-zinc-200 dark:bg-zinc-800" />
              <div className="h-4 flex-1 rounded bg-zinc-100 dark:bg-zinc-800/60" />
              <div className="h-4 w-16 rounded bg-zinc-100 dark:bg-zinc-800/60" />
              <div className="h-4 w-16 rounded bg-zinc-100 dark:bg-zinc-800/60" />
            </div>)}
        </div>
        <div className="px-4 pb-4 text-center text-sm text-zinc-950/60 dark:text-white/60">
          Loading the model catalog…
        </div>
      </div>;
  }
  if (error) {
    return <div className="not-prose p-10 text-center border border-red-200 dark:border-red-900 rounded-xl bg-red-50 dark:bg-red-950/40">
        <div className="font-medium text-red-700 dark:text-red-400">Couldn't load the model catalog</div>
        <div className="text-sm text-red-600/80 dark:text-red-400/70 mt-1">{error} — refresh the page to try again.</div>
      </div>;
  }
  if (!models || models.length === 0) {
    return <div className="not-prose p-10 text-center border border-zinc-950/10 dark:border-white/10 rounded-xl">
        <div className="text-zinc-950/70 dark:text-white/70">No models available</div>
      </div>;
  }
  return <div className="w-full not-prose flex flex-col">
      <div className="sticky top-0 z-20 pt-4 pb-3 bg-white dark:bg-zinc-950">
        <div className="flex flex-wrap gap-2">
          <div className="relative flex-1 min-w-[240px]">
            <svg className="absolute left-3 top-3 -translate-y-1/2 w-4 h-4 text-zinc-950/40 dark:text-white/40 pointer-events-none" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-4.35-4.35M17 10.5a6.5 6.5 0 11-13 0 6.5 6.5 0 0113 0z" />
            </svg>
            <input type="text" placeholder="Search models, providers, or any field..." value={searchTerm} onChange={e => setSearchTerm(e.target.value)} className="w-full pl-9 pr-3 py-2 text-sm rounded-lg border border-zinc-950/15 dark:border-white/15 bg-white dark:bg-zinc-950 text-zinc-950 dark:text-white placeholder:text-zinc-950/40 dark:placeholder:text-white/40 focus:outline-none focus:border-[#0C3B43] dark:focus:border-[#07C983] focus:ring-1 focus:ring-[#0C3B43] dark:focus:ring-[#07C983] transition-colors" />
          </div>
          <select value={selectedProvider} onChange={e => setSelectedProvider(e.target.value)} className="px-3 py-2 text-sm rounded-lg border border-zinc-950/15 dark:border-white/15 bg-white dark:bg-zinc-950 text-zinc-950 dark:text-white focus:outline-none focus:border-[#0C3B43] dark:focus:border-[#07C983] transition-colors capitalize">
            <option value="all">All providers</option>
            {providers.map(provider => <option key={provider} value={provider}>
                {provider}
              </option>)}
          </select>
          <select value={selectedMode} onChange={e => setSelectedMode(e.target.value)} className="px-3 py-2 text-sm rounded-lg border border-zinc-950/15 dark:border-white/15 bg-white dark:bg-zinc-950 text-zinc-950 dark:text-white focus:outline-none focus:border-[#0C3B43] dark:focus:border-[#07C983] transition-colors">
            <option value="all">All modes</option>
            {modes.map(mode => <option key={mode} value={mode}>
                {formatColumnName(mode)}
              </option>)}
          </select>
        </div>
        <div className="mt-2 text-xs text-zinc-950/50 dark:text-white/50 tabular-nums">
          {filteredModels.length.toLocaleString()} of {models.length.toLocaleString()} models
          {providers.length > 0 && <span> across {providers.length} providers</span>}
        </div>
      </div>

      <div className="catalog-scroll overflow-auto border border-zinc-950/10 dark:border-white/10 rounded-xl" style={{
    maxHeight: '640px'
  }}>
        <table className="text-sm bg-white dark:bg-zinc-950" style={{
    display: 'table',
    width: '100%',
    maxWidth: '100%',
    margin: 0,
    borderCollapse: 'collapse',
    tableLayout: 'fixed',
    overflow: 'visible'
  }}>
          <thead className="sticky bg-zinc-50 dark:bg-zinc-900" style={{
    top: 0,
    zIndex: 10
  }}>
            <tr className="text-[11px] uppercase tracking-wider text-zinc-950/50 dark:text-white/50">
              <th className="px-4 py-3 text-left font-semibold border-b border-zinc-950/10 dark:border-white/10 whitespace-nowrap" style={{
    width: '160px'
  }}>
                Provider
              </th>
              <th className="px-4 py-3 text-left font-semibold border-b border-zinc-950/10 dark:border-white/10">
                Model
              </th>
              <th className="px-4 py-3 text-left font-semibold border-b border-zinc-950/10 dark:border-white/10 whitespace-nowrap" style={{
    width: '160px'
  }}>
                Mode
              </th>
              <th className="px-4 py-3 text-right font-semibold border-b border-zinc-950/10 dark:border-white/10 whitespace-nowrap" style={{
    width: '100px'
  }}>
                In / 1M
              </th>
              <th className="px-4 py-3 text-right font-semibold border-b border-zinc-950/10 dark:border-white/10 whitespace-nowrap" style={{
    width: '100px'
  }}>
                Out / 1M
              </th>
              <th className="border-b border-zinc-950/10 dark:border-white/10" style={{
    width: '40px'
  }} />
            </tr>
          </thead>
          <tbody>
            {filteredModels.length === 0 ? <tr>
                <td colSpan={6} className="px-4 py-12 text-center text-zinc-950/60 dark:text-white/60">
                  No models match these filters. Clear the search or switch provider and mode back to "All".
                </td>
              </tr> : visibleModels.map(model => <tr key={model.provider + '-' + model.model} onClick={() => setSelectedModel(model)} className="cursor-pointer transition-colors hover:bg-zinc-50 dark:hover:bg-zinc-900/60">
                  <td className="px-4 py-2.5 text-zinc-950/70 dark:text-white/70 border-b border-zinc-950/5 dark:border-white/5 capitalize whitespace-nowrap overflow-hidden text-ellipsis" title={model.provider || undefined}>
                    {model.provider || '—'}
                  </td>
                  <td className="px-4 py-2.5 border-b border-zinc-950/5 dark:border-white/5">
                    <span className="font-mono text-[13px] text-zinc-950 dark:text-white break-all">
                      {model.model || '—'}
                    </span>
                  </td>
                  <td className="px-4 py-2.5 border-b border-zinc-950/5 dark:border-white/5 whitespace-nowrap">
                    {model.mode ? <span className="inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium bg-zinc-100 text-zinc-600 dark:bg-zinc-800/80 dark:text-zinc-300">
                        {formatColumnName(model.mode)}
                      </span> : <span className="text-zinc-950/40 dark:text-white/40">—</span>}
                  </td>
                  <td className="px-4 py-2.5 text-right text-zinc-950/70 dark:text-white/70 border-b border-zinc-950/5 dark:border-white/5 tabular-nums whitespace-nowrap">
                    {formatCostPerMillion(model.input_cost_per_token) || '—'}
                  </td>
                  <td className="px-4 py-2.5 text-right text-zinc-950/70 dark:text-white/70 border-b border-zinc-950/5 dark:border-white/5 tabular-nums whitespace-nowrap">
                    {formatCostPerMillion(model.output_cost_per_token) || '—'}
                  </td>
                  <td className="px-2 py-2.5 border-b border-zinc-950/5 dark:border-white/5 text-zinc-950/30 dark:text-white/30">
                    <svg className="w-4 h-4 mx-auto" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
                    </svg>
                  </td>
                </tr>)}
          </tbody>
        </table>
        {filteredModels.length > visibleCount && <div className="sticky bottom-0 flex items-center justify-center gap-3 px-4 py-3 bg-white/95 dark:bg-zinc-950/95 backdrop-blur border-t border-zinc-950/10 dark:border-white/10">
            <span className="text-xs text-zinc-950/50 dark:text-white/50 tabular-nums">
              Showing {visibleModels.length.toLocaleString()} of {filteredModels.length.toLocaleString()}
            </span>
            <button onClick={() => setVisibleCount(c => c + 200)} className="px-3 py-1.5 text-xs font-medium rounded-lg text-white bg-[#0C3B43] hover:bg-[#0a2f35] dark:bg-[#07C983]/15 dark:text-[#07C983] dark:hover:bg-[#07C983]/25 transition-colors">
              Load more models
            </button>
          </div>}
      </div>

      <ModelDialog model={selectedModel} onClose={() => setSelectedModel(null)} />
    </div>;
};

<ModelsCatalog />
