File size: 4,271 Bytes
064bfd6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da64cea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
064bfd6
da64cea
064bfd6
 
 
 
 
 
 
 
 
38d7632
064bfd6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78e2eb9
 
 
 
15aad85
78e2eb9
 
15aad85
 
 
 
78e2eb9
 
 
064bfd6
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import type { ModelOption } from './modelOptions.js'

let openRouterModelsCache: ModelOption[] | null = null
let cacheTimestamp: number = 0
let isFetching = false
const CACHE_DURATION = 5 * 60 * 1000 // 5 minutes

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[]> {
  // Check cache
  if (
    openRouterModelsCache !== null &&
    Date.now() - cacheTimestamp < CACHE_DURATION
  ) {
    return openRouterModelsCache
  }

  // Prevent concurrent fetches
  if (isFetching) {
    return []
  }

  isFetching = true

  // Import auth utilities to get API key
  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 || []

    // Filter out non-model items (routers, aggregators, etc.)
    const validModels = models.filter(model => {
      const id = model.id.toLowerCase()
      const name = model.name.toLowerCase()

      // Skip items that are routers, aggregators, or services
      const skipPatterns = [
        'router',
        'aggregator',
        'aggregation',
        'free.*router',
        'routing',
        'service',
        'platform',
        'hub',
      ]

      // Skip if ID or name matches any skip pattern
      for (const pattern of skipPatterns) {
        if (new RegExp(pattern).test(id) || new RegExp(pattern).test(name)) {
          return false
        }
      }

      // Skip items without proper model ID format (should contain provider/model)
      if (!id.includes('/') || id.startsWith('router') || id.startsWith('free')) {
        return false
      }

      // Skip items without pricing information
      if (!model.pricing || !model.pricing.prompt || !model.pricing.completion) {
        return false
      }

      return true
    })

    // Convert to ModelOption format
    const options: ModelOption[] = validModels.map(model => {
      // Truncate description if too long
      let description = model.description || model.id
      if (description.length > 100) {
        description = description.substring(0, 97) + '...'
      }

      return {
        value: model.id,
        label: model.name,
        description: description,
      }
    })

    // Update cache
    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 => {
    // Check if this model is free by looking at the model name
    // Models with ":free" or "(free)" in the name are free
    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
}

// Start background fetch if OpenRouter is configured
export function startOpenRouterModelsFetch(): void {
  // Non-blocking background fetch
  void fetchOpenRouterModels()
}