import type { ProviderEntry, ProviderFetcher } from './types'; interface HFRouterModel { id: string; object: string; created: number; owned_by: string; providers?: HFRouterProvider[]; } interface HFRouterProvider { provider: string; status?: "live" | "offline" | "staging" | "deprecated"; context_length?: number; pricing?: { input: number; // cents per million tokens output: number; // cents per million tokens }; supports_tools?: boolean; supports_structured_output?: boolean; } export class HuggingFaceRouterFetcher implements ProviderFetcher { name = 'huggingface-router'; async fetchModels(): Promise { try { const response = await fetch('https://router.huggingface.co/v1/models'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json() as { data: HFRouterModel[] }; return this.normalizeModels(data.data); } catch (error) { console.error('Failed to fetch HuggingFace router models:', error); throw error; } } private normalizeModels(models: HFRouterModel[]): ProviderEntry[] { const entries: ProviderEntry[] = []; for (const model of models) { if (!model.providers) continue; for (const provider of model.providers) { const entry: ProviderEntry = { provider: this.normalizeProviderName(provider.provider), model_id: model.id, owned_by: model.owned_by, created: model.created, }; // Set status if (provider.status) { entry.status = provider.status === "staging" ? "offline" : provider.status; } // Convert pricing from cents to dollars per million tokens if (provider.pricing) { entry.pricing = { input: provider.pricing.input / 100, // cents to dollars output: provider.pricing.output / 100, // cents to dollars }; } // Copy context length if (provider.context_length) { entry.context_length = provider.context_length; } // Copy capability flags if (provider.supports_tools !== undefined) { entry.supports_tools = provider.supports_tools; } if (provider.supports_structured_output !== undefined) { entry.supports_structured_output = provider.supports_structured_output; } entries.push(entry); } } return entries; } private normalizeProviderName(providerName: string): string { // Map HF router provider names to our standard names const providerMap: Record = { 'featherless-ai': 'featherless', 'fireworks-ai': 'fireworks', 'hf-inference': 'huggingface', // Keep others as-is }; return providerMap[providerName] || providerName; } } // Helper function to extract HF router data from a model export function extractHFRouterData(model: any): Map { const providerMap = new Map(); if (!model.providers || !Array.isArray(model.providers)) { return providerMap; } for (const provider of model.providers) { if (!provider.provider) continue; const entry: ProviderEntry = { provider: provider.provider, }; // Set status if (provider.status) { entry.status = provider.status === "staging" ? "offline" : provider.status; } // Convert pricing from cents to dollars if needed if (provider.pricing) { // Check if pricing is already in dollars (values < 100 likely dollars) const needsConversion = provider.pricing.input >= 100 || provider.pricing.output >= 100; entry.pricing = { input: needsConversion ? provider.pricing.input / 100 : provider.pricing.input, output: needsConversion ? provider.pricing.output / 100 : provider.pricing.output, }; } // Copy other fields if (provider.context_length) { entry.context_length = provider.context_length; } if (provider.supports_tools !== undefined) { entry.supports_tools = provider.supports_tools; } if (provider.supports_structured_output !== undefined) { entry.supports_structured_output = provider.supports_structured_output; } providerMap.set(provider.provider, entry); } return providerMap; }