| import type { ModelOption } from './modelOptions.js' |
|
|
| let openRouterModelsCache: ModelOption[] | null = null |
| let cacheTimestamp: number = 0 |
| let isFetching = false |
| const CACHE_DURATION = 5 * 60 * 1000 |
|
|
| export interface OpenRouterModel { |
| id: string |
| name: string |
| description: string |
| context_length: number |
| pricing: { |
| prompt: string |
| completion: string |
| } |
| } |
|
|
| function formatPrice(price: string): string { |
| const numPrice = parseFloat(price) |
| if (numPrice === 0) return 'Free' |
| if (numPrice < 0.000001) return '$<0.000001' |
| return `$${price}` |
| } |
|
|
| export async function fetchOpenRouterModels( |
| apiKey?: string, |
| ): Promise<ModelOption[]> { |
| |
| if ( |
| openRouterModelsCache !== null && |
| Date.now() - cacheTimestamp < CACHE_DURATION |
| ) { |
| return openRouterModelsCache |
| } |
|
|
| |
| if (isFetching) { |
| return [] |
| } |
|
|
| isFetching = true |
|
|
| |
| const { getGlobalConfig } = await import('../config.js') |
| const config = getGlobalConfig() |
| const key = apiKey || config.openRouterApiKey |
|
|
| if (!key) { |
| isFetching = false |
| return [] |
| } |
|
|
| try { |
| const response = await fetch('https://openrouter.ai/api/v1/models', { |
| headers: { |
| Authorization: `Bearer ${key}`, |
| }, |
| }) |
|
|
| if (!response.ok) { |
| throw new Error(`Failed to fetch models: ${response.statusText}`) |
| } |
|
|
| const data = await response.json() |
| const models: OpenRouterModel[] = data.data || [] |
|
|
| |
| const validModels = models.filter(model => { |
| const id = model.id.toLowerCase() |
| const name = model.name.toLowerCase() |
|
|
| |
| const skipPatterns = [ |
| 'router', |
| 'aggregator', |
| 'aggregation', |
| 'free.*router', |
| 'routing', |
| 'service', |
| 'platform', |
| 'hub', |
| ] |
|
|
| |
| for (const pattern of skipPatterns) { |
| if (new RegExp(pattern).test(id) || new RegExp(pattern).test(name)) { |
| return false |
| } |
| } |
|
|
| |
| if (!id.includes('/') || id.startsWith('router') || id.startsWith('free')) { |
| return false |
| } |
|
|
| |
| if (!model.pricing || !model.pricing.prompt || !model.pricing.completion) { |
| return false |
| } |
|
|
| return true |
| }) |
|
|
| |
| const options: ModelOption[] = validModels.map(model => { |
| |
| let description = model.description || model.id |
| if (description.length > 100) { |
| description = description.substring(0, 97) + '...' |
| } |
|
|
| return { |
| value: model.id, |
| label: model.name, |
| description: description, |
| } |
| }) |
|
|
| |
| openRouterModelsCache = options |
| cacheTimestamp = Date.now() |
|
|
| return options |
| } catch (error) { |
| console.error('Failed to fetch OpenRouter models:', error) |
| return [] |
| } finally { |
| isFetching = false |
| } |
| } |
|
|
| export function getCachedOpenRouterModels(): ModelOption[] { |
| return openRouterModelsCache || [] |
| } |
|
|
| export function hasOpenRouterModelsCache(): boolean { |
| return openRouterModelsCache !== null |
| } |
|
|
| export function getFirstFreeModel(): ModelOption | null { |
| if (!openRouterModelsCache || openRouterModelsCache.length === 0) { |
| return null |
| } |
|
|
| return openRouterModelsCache.find(model => { |
| |
| |
| const label = (model.label || '').toLowerCase() |
| const value = (model.value || '').toLowerCase() |
| return label.includes(':free') || label.includes('(free)') || value.includes(':free') || value.includes('(free)') |
| }) || null |
| } |
|
|
| export function clearOpenRouterModelsCache(): void { |
| openRouterModelsCache = null |
| cacheTimestamp = 0 |
| } |
|
|
| |
| export function startOpenRouterModelsFetch(): void { |
| |
| void fetchOpenRouterModels() |
| } |