chenbhao commited on
Commit
66e6100
·
1 Parent(s): 278cb48

fix: refresh model list

Browse files
src/commands/login/login.tsx CHANGED
@@ -71,48 +71,36 @@ export async function call(
71
  context.setMessages(stripSignatureBlocks);
72
 
73
  if (success) {
74
- // 这些 reset 的本质: 换账号 = 全部重置
75
- // Post-login refresh logic. Keep in sync with onboarding in src/interactiveHelpers.tsx
76
- // 成本统计 Reset cost state when switching accounts
77
  resetCostState();
78
-
79
- // 远程配置 Refresh remotely managed settings after login (non-blocking)
80
  void refreshRemoteManagedSettings();
81
- // Refresh policy limits after login (non-blocking)
82
  void refreshPolicyLimits();
83
-
84
- // 用户缓存 Clear user data cache BEFORE GrowthBook refresh so it picks up fresh credentials
85
  resetUserCache();
86
-
87
- // Feature flags(GrowthBook) Refresh GrowthBook after login to get updated feature flags (e.g., for claude.ai MCPs)
88
  refreshGrowthBookAfterAuthChange();
89
-
90
- // 设备信任 记住这台设备
91
- // Clear any stale trusted device token from a previous account before
92
- // re-enrolling — prevents sending the old token on bridge calls while
93
- // the async enrollTrustedDevice() is in-flight.
94
  clearTrustedDeviceToken();
95
- // Enroll as a trusted device for Remote Control (10-min fresh-session window)
96
  void enrollTrustedDevice();
97
-
98
- // 权限系统(killswitch) 防止越权操作, 自动模式滥用
99
- // Reset killswitch gate checks and re-run with new org
100
  resetBypassPermissionsCheck();
101
  const appState = context.getAppState();
102
  void checkAndDisableBypassPermissionsIfNeeded(appState.toolPermissionContext, context.setAppState);
103
 
104
- // 自动模式 gating, Feature flag 控制功能开关
105
  if (feature('TRANSCRIPT_CLASSIFIER')) {
106
  resetAutoModeGateCheck();
107
  void checkAndDisableAutoModeIfNeeded(appState.toolPermissionContext, context.setAppState, appState.fastMode);
108
  }
109
 
110
- // Increment authVersion to trigger re-fetching of auth-dependent data in hooks (e.g., MCP servers)
 
 
 
 
 
 
 
111
  context.setAppState(prev => ({
112
  ...prev,
113
- authVersion: prev.authVersion + 1, // 触发全局更新, 用 version 触发所有 hook 重新 fetch
114
- mainLoopModel: null, // 重置为 null,使用新 provider 的默认模型
115
- mainLoopModelForSession: null, // 重置 session 模型
116
  }));
117
  }
118
 
 
71
  context.setMessages(stripSignatureBlocks);
72
 
73
  if (success) {
74
+ // These reset: switching account = full reset
 
 
75
  resetCostState();
 
 
76
  void refreshRemoteManagedSettings();
 
77
  void refreshPolicyLimits();
 
 
78
  resetUserCache();
 
 
79
  refreshGrowthBookAfterAuthChange();
 
 
 
 
 
80
  clearTrustedDeviceToken();
 
81
  void enrollTrustedDevice();
 
 
 
82
  resetBypassPermissionsCheck();
83
  const appState = context.getAppState();
84
  void checkAndDisableBypassPermissionsIfNeeded(appState.toolPermissionContext, context.setAppState);
85
 
 
86
  if (feature('TRANSCRIPT_CLASSIFIER')) {
87
  resetAutoModeGateCheck();
88
  void checkAndDisableAutoModeIfNeeded(appState.toolPermissionContext, context.setAppState, appState.fastMode);
89
  }
90
 
91
+ // For OpenCode provider, pre-fetch models so /model shows them immediately
92
+ const { getAPIProvider } = await import('../../utils/model/providers.js')
93
+ if (getAPIProvider() === 'opencode') {
94
+ const { fetchOpencodeModels } = await import('../../services/api/opencodeClient.js')
95
+ void fetchOpencodeModels()
96
+ }
97
+
98
+ // Increment authVersion to trigger re-fetching of auth-dependent data
99
  context.setAppState(prev => ({
100
  ...prev,
101
+ authVersion: prev.authVersion + 1,
102
+ mainLoopModel: null,
103
+ mainLoopModelForSession: null,
104
  }));
105
  }
106
 
src/services/api/opencodeClient.ts CHANGED
@@ -8,42 +8,38 @@ import {
8
 
9
  const OPENCODE_BASE_URL = 'https://opencode.ai/zen/v1'
10
 
11
- let cachedModels: Array<{ id: string; name?: string }> | null = null
 
 
 
 
 
 
12
  let fetchPromise: Promise<void> | null = null
13
 
14
  export async function fetchOpencodeModels(): Promise<void> {
15
- if (cachedModels || fetchPromise) return
16
 
17
  fetchPromise = (async () => {
18
  try {
19
- const https = await import('https')
20
- const data = await new Promise<string>((resolve, reject) => {
21
- const apiKey = getOpenCodeApiKey()
22
- const headers: Record<string, string> = {
23
- 'User-Agent': 'claude-code/2.1.88',
24
- }
25
- if (apiKey) {
26
- headers.Authorization = `Bearer ${apiKey}`
27
- }
28
- const req = https.get(`${OPENCODE_BASE_URL}/models`, {
29
- headers,
30
- timeout: 15000,
31
- }, res => {
32
- let body = ''
33
- res.on('data', chunk => { body += chunk })
34
- res.on('end', () => resolve(body))
35
- res.on('error', reject)
36
- })
37
- req.on('error', reject)
38
- req.on('timeout', () => {
39
- req.destroy()
40
- reject(new Error('Request timed out'))
41
- })
42
- })
43
 
44
- const parsed = JSON.parse(data) as { data?: Array<{ id: string; name?: string }> }
45
- if (Array.isArray(parsed.data)) {
46
- cachedModels = parsed.data.map(m => ({ id: m.id, name: m.name || m.id }))
 
 
 
 
47
  }
48
  } catch {
49
  // Ignore errors
@@ -55,7 +51,7 @@ export async function fetchOpencodeModels(): Promise<void> {
55
  await fetchPromise
56
  }
57
 
58
- export function getCachedOpencodeModels(): Array<{ id: string; name?: string }> {
59
  return cachedModels || []
60
  }
61
 
 
8
 
9
  const OPENCODE_BASE_URL = 'https://opencode.ai/zen/v1'
10
 
11
+ // Known free models that don't have -free suffix
12
+ const FREE_MODEL_IDS = new Set([
13
+ 'big-pickle',
14
+ 'gpt-5-nano',
15
+ ])
16
+
17
+ let cachedModels: Array<{ id: string; name?: string; isFree: boolean }> | null = null
18
  let fetchPromise: Promise<void> | null = null
19
 
20
  export async function fetchOpencodeModels(): Promise<void> {
21
+ if (fetchPromise) return
22
 
23
  fetchPromise = (async () => {
24
  try {
25
+ const apiKey = getOpenCodeApiKey()
26
+ const headers: Record<string, string> = {
27
+ 'User-Agent': 'claude-code/2.1.88',
28
+ }
29
+ if (apiKey) {
30
+ headers.Authorization = `Bearer ${apiKey}`
31
+ }
32
+
33
+ const res = await fetch(`${OPENCODE_BASE_URL}/models`, { headers })
34
+ if (!res.ok) return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ const data = await res.json() as { data?: Array<{ id: string; name?: string }> }
37
+ if (Array.isArray(data.data)) {
38
+ cachedModels = data.data.map(m => ({
39
+ id: m.id,
40
+ name: m.name || m.id,
41
+ isFree: m.id.endsWith('-free') || FREE_MODEL_IDS.has(m.id),
42
+ }))
43
  }
44
  } catch {
45
  // Ignore errors
 
51
  await fetchPromise
52
  }
53
 
54
+ export function getCachedOpencodeModels(): Array<{ id: string; name?: string; isFree: boolean }> {
55
  return cachedModels || []
56
  }
57
 
src/utils/model/modelOptions.ts CHANGED
@@ -491,7 +491,7 @@ function getModelOptionsBase(fastMode = false): ModelOption[] {
491
  }
492
 
493
  // OpenCode Zen: Fetch models dynamically from API
494
- // If no API key, show only free models (ending with -free)
495
  // If API key is provided, show all models except free ones
496
  if (getAPIProvider() === 'opencode') {
497
  const defaultOpt = getDefaultOptionForUser(fastMode)
@@ -503,8 +503,8 @@ function getModelOptionsBase(fastMode = false): ModelOption[] {
503
 
504
  if (models && Array.isArray(models) && models.length > 0) {
505
  const filtered = hasApiKey
506
- ? models.filter(m => !m.id.endsWith('-free'))
507
- : models.filter(m => m.id.endsWith('-free'))
508
 
509
  if (filtered.length > 0) {
510
  return [
 
491
  }
492
 
493
  // OpenCode Zen: Fetch models dynamically from API
494
+ // If no API key, show only free models
495
  // If API key is provided, show all models except free ones
496
  if (getAPIProvider() === 'opencode') {
497
  const defaultOpt = getDefaultOptionForUser(fastMode)
 
503
 
504
  if (models && Array.isArray(models) && models.length > 0) {
505
  const filtered = hasApiKey
506
+ ? models.filter(m => !m.isFree)
507
+ : models.filter(m => m.isFree)
508
 
509
  if (filtered.length > 0) {
510
  return [