chenbhao commited on
Commit
15aad85
·
1 Parent(s): 78e2eb9

feat: auto load free openrouter model

Browse files
src/commands/login/login.tsx CHANGED
@@ -109,7 +109,9 @@ export async function call(
109
  // Increment authVersion to trigger re-fetching of auth-dependent data in hooks (e.g., MCP servers)
110
  context.setAppState(prev => ({
111
  ...prev,
112
- authVersion: prev.authVersion + 1 // 触发全局更新, 用 version 触发所有 hook 重新 fetch
 
 
113
  }));
114
  }
115
 
 
109
  // Increment authVersion to trigger re-fetching of auth-dependent data in hooks (e.g., MCP servers)
110
  context.setAppState(prev => ({
111
  ...prev,
112
+ authVersion: prev.authVersion + 1, // 触发全局更新, 用 version 触发所有 hook 重新 fetch
113
+ mainLoopModel: null, // 重置为 null,使用新 provider 的默认模型
114
+ mainLoopModelForSession: null, // 重置 session 模型
115
  }));
116
  }
117
 
src/components/OpenRouterLoginFlow.tsx CHANGED
@@ -29,13 +29,15 @@ export function OpenRouterLoginFlow({
29
 
30
  async function handleSubmit(value?: string): Promise<void> {
31
  if (!value && !existingKey) {
 
32
  return
33
  }
34
-
35
  const trimmed = value?.trim() || ''
36
- const keyToSave = trimmed || existingKey
37
-
38
  if (!keyToSave) {
 
39
  return
40
  }
41
 
 
29
 
30
  async function handleSubmit(value?: string): Promise<void> {
31
  if (!value && !existingKey) {
32
+ setStatus('Please enter an API key or press Esc to cancel')
33
  return
34
  }
35
+
36
  const trimmed = value?.trim() || ''
37
+ const keyToSave = trimmed || existingKey || ''
38
+
39
  if (!keyToSave) {
40
+ setStatus('Please enter an API key or press Esc to cancel')
41
  return
42
  }
43
 
src/components/SearchableModelPicker.tsx CHANGED
@@ -104,9 +104,9 @@ export function SearchableModelPicker({
104
  return filteredOptions
105
  }, [filteredOptions, initial])
106
 
107
- const initialFocusValue = Array.isArray(optionsWithInitial) && optionsWithInitial.some(opt => opt.value === initialValue)
108
  ? initialValue
109
- : (Array.isArray(optionsWithInitial) && optionsWithInitial[0]?.value) ?? undefined
110
 
111
  // Helper functions
112
  function resolveOptionModel(value?: string): string | undefined {
 
104
  return filteredOptions
105
  }, [filteredOptions, initial])
106
 
107
+ const initialFocusValue = Array.isArray(filteredOptions) && filteredOptions.some(opt => opt.value === initialValue)
108
  ? initialValue
109
+ : (Array.isArray(filteredOptions) && filteredOptions[0]?.value) ?? undefined
110
 
111
  // Helper functions
112
  function resolveOptionModel(value?: string): string | undefined {
src/utils/auth.ts CHANGED
@@ -404,17 +404,26 @@ export async function saveOpenRouterApiKey(apiKey: string): Promise<void> {
404
  'Invalid API key: API key must be a non-empty string.',
405
  )
406
  }
407
-
408
- if (!isValidApiKey(apiKey)) {
 
 
 
 
 
 
 
 
 
409
  throw new Error(
410
  'Invalid API key format. API key must contain only alphanumeric characters, dashes, and underscores.',
411
  )
412
  }
413
-
414
  saveGlobalConfig(current => ({
415
  ...current,
416
  authProvider: 'openrouter',
417
- openRouterApiKey: apiKey,
418
  }))
419
  // Clear provider cache so it will be re-read on next access
420
  const { clearStoredProviderCache } = await import('./model/providers.js')
 
404
  'Invalid API key: API key must be a non-empty string.',
405
  )
406
  }
407
+
408
+ // Trim the API key to remove any leading/trailing whitespace
409
+ const trimmedKey = apiKey.trim()
410
+
411
+ if (!trimmedKey) {
412
+ throw new Error(
413
+ 'Invalid API key: API key cannot be empty or whitespace only.',
414
+ )
415
+ }
416
+
417
+ if (!isValidApiKey(trimmedKey)) {
418
  throw new Error(
419
  'Invalid API key format. API key must contain only alphanumeric characters, dashes, and underscores.',
420
  )
421
  }
422
+
423
  saveGlobalConfig(current => ({
424
  ...current,
425
  authProvider: 'openrouter',
426
+ openRouterApiKey: trimmedKey,
427
  }))
428
  // Clear provider cache so it will be re-read on next access
429
  const { clearStoredProviderCache } = await import('./model/providers.js')
src/utils/model/model.ts CHANGED
@@ -178,12 +178,50 @@ export function getRuntimeMainLoopModel(params: {
178
  * @returns The default model setting to use
179
  */
180
  export function getDefaultMainLoopModelSetting(): ModelName | ModelAlias {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  // Check if using Local provider
182
  // Need to check both getAPIProvider() and direct file read because
183
  // getAPIProvider() might return null if cache was cleared
184
- const apiProvider = getAPIProvider()
185
  let isLocalProvider = apiProvider === 'local'
186
-
187
  // If getAPIProvider() returns null, check the config file directly
188
  if (!isLocalProvider && apiProvider === null) {
189
  try {
@@ -200,7 +238,7 @@ export function getDefaultMainLoopModelSetting(): ModelName | ModelAlias {
200
  // Ignore errors, fall through to default behavior
201
  }
202
  }
203
-
204
  if (isLocalProvider) {
205
  const localModelName = getLocalModelName()
206
  if (localModelName) {
 
178
  * @returns The default model setting to use
179
  */
180
  export function getDefaultMainLoopModelSetting(): ModelName | ModelAlias {
181
+ // Check if using OpenRouter provider
182
+ const apiProvider = getAPIProvider()
183
+ let isOpenRouterProvider = apiProvider === 'openrouter'
184
+
185
+ // If getAPIProvider() returns null, check the config file directly
186
+ if (!isOpenRouterProvider && apiProvider === null) {
187
+ try {
188
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
189
+ const { readFileSync } = require('fs') as typeof import('fs')
190
+ const { getGlobalClaudeFile } = require('./env.js') as typeof import('./env.js')
191
+ const raw = readFileSync(getGlobalClaudeFile(), 'utf8')
192
+ const config = JSON.parse(raw) as {
193
+ authProvider?: string
194
+ openRouterApiKey?: string
195
+ }
196
+ isOpenRouterProvider = config.authProvider === 'openrouter' && !!config.openRouterApiKey
197
+ } catch {
198
+ // Ignore errors, fall through to default behavior
199
+ }
200
+ }
201
+
202
+ if (isOpenRouterProvider) {
203
+ // Try to get the first free model from OpenRouter
204
+ try {
205
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
206
+ const openRouterModelsModule = require('./openRouterModels.js') as {
207
+ getFirstFreeModel: () => { value: string } | null
208
+ }
209
+ const firstFreeModel = openRouterModelsModule.getFirstFreeModel()
210
+ if (firstFreeModel) {
211
+ return firstFreeModel.value as ModelName
212
+ }
213
+ } catch {
214
+ // Module not yet loaded or error, fall through to default behavior
215
+ }
216
+ // Fallback to 'default' if no free model found
217
+ return 'default'
218
+ }
219
+
220
  // Check if using Local provider
221
  // Need to check both getAPIProvider() and direct file read because
222
  // getAPIProvider() might return null if cache was cleared
 
223
  let isLocalProvider = apiProvider === 'local'
224
+
225
  // If getAPIProvider() returns null, check the config file directly
226
  if (!isLocalProvider && apiProvider === null) {
227
  try {
 
238
  // Ignore errors, fall through to default behavior
239
  }
240
  }
241
+
242
  if (isLocalProvider) {
243
  const localModelName = getLocalModelName()
244
  if (localModelName) {
src/utils/model/openRouterModels.ts CHANGED
@@ -142,12 +142,13 @@ export function getFirstFreeModel(): ModelOption | null {
142
  if (!openRouterModelsCache || openRouterModelsCache.length === 0) {
143
  return null
144
  }
145
-
146
  return openRouterModelsCache.find(model => {
147
  // Check if this model is free by looking at the model name
148
- // Models with "(free)" in the name are free
149
- const label = model.label || ''
150
- return label.toLowerCase().includes('(free)')
 
151
  }) || null
152
  }
153
 
 
142
  if (!openRouterModelsCache || openRouterModelsCache.length === 0) {
143
  return null
144
  }
145
+
146
  return openRouterModelsCache.find(model => {
147
  // Check if this model is free by looking at the model name
148
+ // Models with ":free" or "(free)" in the name are free
149
+ const label = (model.label || '').toLowerCase()
150
+ const value = (model.value || '').toLowerCase()
151
+ return label.includes(':free') || label.includes('(free)') || value.includes(':free') || value.includes('(free)')
152
  }) || null
153
  }
154