Skip to content

Commit 6c8e6e0

Browse files
committed
fix: 修复 dist 构建产物运行时 API Error 系列问题
1. 修复 API Error: undefined is not an object (evaluating '_OpenAI_instances.add') - build.ts 增加 external: ['openai'],避免 Bun bundler 将 openai SDK 打包进 chunk,防止 private class fields (WeakSet/WeakMap) 懒初始化顺序错乱 2. 修复 API Error: Cannot access 'OpenAI' before initialization - openai/client.ts、grok/client.ts:静态 import OpenAI 改为 import type(仅类型) + 函数体内 await import('openai') 动态加载,彻底规避 Bun code splitting 产生的 TDZ(Temporal Dead Zone)问题 - openai/index.ts、grok/index.ts:对应调用处加 await 3. 修复 API Error: undefined is not an object (evaluating 'DEFAULT_MODEL_MAP[cleanModel]') - openai/modelMapping.ts、grok/modelMapping.ts:将模块级常量 DEFAULT_MODEL_MAP / DEFAULT_FAMILY_MAP 改为函数 getDefaultModelMap() / getDefaultFamilyMap(), 避免模块初始化时因 Bun 懒加载顺序导致常量为 undefined - 包括首次登录(/login)逻辑和后续再次进入界面的逻辑均已覆盖 4. 登录后模型字符串缓存重置(login.tsx) - /login 成功后依次调用 resetSettingsCache()、applyConfigEnvironmentVariables()、 resetModelStrings(),确保切换 provider 后模型映射立即生效,不残留旧缓存 5. 新增 resetModelStrings() 工具函数(modelStrings.ts) - 暴露缓存重置接口,供 login 流程调用 6. 错误日志增强(openai/index.ts、grok/index.ts) - catch 块补充 error.stack 输出,便于定位构建产物中的运行时错误
1 parent dad3ad2 commit 6c8e6e0

9 files changed

Lines changed: 69 additions & 46 deletions

File tree

build.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const result = await Bun.build({
5959
splitting: true,
6060
define: getMacroDefines(),
6161
features,
62+
external: ['openai'],
6263
})
6364

6465
if (!result.success) {

src/commands/login/login.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import {
2222
resetAutoModeGateCheck,
2323
resetBypassPermissionsCheck,
2424
} from '../../utils/permissions/bypassPermissionsKillswitch.js'
25+
import { applyConfigEnvironmentVariables } from '../../utils/managedEnv.js'
26+
import { resetModelStrings } from '../../utils/model/modelStrings.js'
27+
import { resetSettingsCache } from '../../utils/settings/settingsCache.js'
2528
import { resetUserCache } from '../../utils/user.js'
2629

2730
export async function call(
@@ -40,6 +43,9 @@ export async function call(
4043
// Reset cost state when switching accounts
4144
resetCostState()
4245
// Refresh remotely managed settings after login (non-blocking)
46+
resetSettingsCache()
47+
applyConfigEnvironmentVariables()
48+
resetModelStrings()
4349
void refreshRemoteManagedSettings()
4450
// Refresh policy limits after login (non-blocking)
4551
void refreshPolicyLimits()

src/services/api/grok/client.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import OpenAI from 'openai'
1+
import type OpenAI from 'openai'
22
import { getProxyFetchOptions } from 'src/utils/proxy.js'
33

44
/**
@@ -12,17 +12,18 @@ const DEFAULT_BASE_URL = 'https://api.x.ai/v1'
1212

1313
let cachedClient: OpenAI | null = null
1414

15-
export function getGrokClient(options?: {
15+
export async function getGrokClient(options?: {
1616
maxRetries?: number
1717
fetchOverride?: typeof fetch
1818
source?: string
19-
}): OpenAI {
19+
}): Promise<OpenAI> {
2020
if (cachedClient) return cachedClient
2121

22+
const { default: OpenAIClass } = await import('openai')
2223
const apiKey = process.env.GROK_API_KEY || process.env.XAI_API_KEY || ''
2324
const baseURL = process.env.GROK_BASE_URL || DEFAULT_BASE_URL
2425

25-
const client = new OpenAI({
26+
const client = new OpenAIClass({
2627
apiKey,
2728
baseURL,
2829
maxRetries: options?.maxRetries ?? 0,

src/services/api/grok/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export async function* queryModelGrok(
6565
const openaiTools = anthropicToolsToOpenAI(standardTools)
6666
const openaiToolChoice = anthropicToolChoiceToOpenAI(options.toolChoice)
6767

68-
const client = getGrokClient({
68+
const client = await getGrokClient({
6969
maxRetries: 0,
7070
fetchOverride: options.fetchOverride as typeof fetch | undefined,
7171
source: options.querySource,
@@ -187,7 +187,8 @@ export async function* queryModelGrok(
187187
}
188188
} catch (error) {
189189
const errorMessage = error instanceof Error ? error.message : String(error)
190-
logForDebugging(`[Grok] Error: ${errorMessage}`, { level: 'error' })
190+
const stack = error instanceof Error ? `\n${error.stack}` : ''
191+
logForDebugging(`[Grok] Error: ${errorMessage}${stack}`, { level: 'error' })
191192
yield createAssistantAPIErrorMessage({
192193
content: `API Error: ${errorMessage}`,
193194
apiError: 'api_error',

src/services/api/grok/modelMapping.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,31 @@
55
* or override the entire mapping via GROK_MODEL_MAP env var (JSON string):
66
* GROK_MODEL_MAP='{"opus":"grok-4","sonnet":"grok-3","haiku":"grok-3-mini-fast"}'
77
*/
8-
const DEFAULT_MODEL_MAP: Record<string, string> = {
9-
'claude-sonnet-4-20250514': 'grok-3-mini-fast',
10-
'claude-sonnet-4-5-20250929': 'grok-3-mini-fast',
11-
'claude-sonnet-4-6': 'grok-3-mini-fast',
12-
'claude-opus-4-20250514': 'grok-4.20-reasoning',
13-
'claude-opus-4-1-20250805': 'grok-4.20-reasoning',
14-
'claude-opus-4-5-20251101': 'grok-4.20-reasoning',
15-
'claude-opus-4-6': 'grok-4.20-reasoning',
16-
'claude-haiku-4-5-20251001': 'grok-3-mini-fast',
17-
'claude-3-5-haiku-20241022': 'grok-3-mini-fast',
18-
'claude-3-7-sonnet-20250219': 'grok-3-mini-fast',
19-
'claude-3-5-sonnet-20241022': 'grok-3-mini-fast',
8+
function getDefaultModelMap(): Record<string, string> {
9+
return {
10+
'claude-sonnet-4-20250514': 'grok-3-mini-fast',
11+
'claude-sonnet-4-5-20250929': 'grok-3-mini-fast',
12+
'claude-sonnet-4-6': 'grok-3-mini-fast',
13+
'claude-opus-4-20250514': 'grok-4.20-reasoning',
14+
'claude-opus-4-1-20250805': 'grok-4.20-reasoning',
15+
'claude-opus-4-5-20251101': 'grok-4.20-reasoning',
16+
'claude-opus-4-6': 'grok-4.20-reasoning',
17+
'claude-haiku-4-5-20251001': 'grok-3-mini-fast',
18+
'claude-3-5-haiku-20241022': 'grok-3-mini-fast',
19+
'claude-3-7-sonnet-20250219': 'grok-3-mini-fast',
20+
'claude-3-5-sonnet-20241022': 'grok-3-mini-fast',
21+
}
2022
}
2123

2224
/**
2325
* Family-level mapping defaults (used by GROK_MODEL_MAP).
2426
*/
25-
const DEFAULT_FAMILY_MAP: Record<string, string> = {
26-
opus: 'grok-4.20-reasoning',
27-
sonnet: 'grok-3-mini-fast',
28-
haiku: 'grok-3-mini-fast',
27+
function getDefaultFamilyMap(): Record<string, string> {
28+
return {
29+
opus: 'grok-4.20-reasoning',
30+
sonnet: 'grok-3-mini-fast',
31+
haiku: 'grok-3-mini-fast',
32+
}
2933
}
3034

3135
function getModelFamily(model: string): 'haiku' | 'sonnet' | 'opus' | null {
@@ -93,13 +97,13 @@ export function resolveGrokModel(anthropicModel: string): string {
9397
}
9498

9599
// 5. Exact model name lookup
96-
if (DEFAULT_MODEL_MAP[cleanModel]) {
97-
return DEFAULT_MODEL_MAP[cleanModel]
100+
if (getDefaultModelMap()[cleanModel]) {
101+
return getDefaultModelMap()[cleanModel]
98102
}
99103

100104
// 6. Family-level default
101-
if (family && DEFAULT_FAMILY_MAP[family]) {
102-
return DEFAULT_FAMILY_MAP[family]
105+
if (family && getDefaultFamilyMap()[family]) {
106+
return getDefaultFamilyMap()[family]
103107
}
104108

105109
// 7. Pass through

src/services/api/openai/client.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import OpenAI from 'openai'
1+
import type OpenAI from 'openai'
22
import { getProxyFetchOptions } from 'src/utils/proxy.js'
33
import { isEnvTruthy } from 'src/utils/envUtils.js'
44

@@ -13,17 +13,18 @@ import { isEnvTruthy } from 'src/utils/envUtils.js'
1313

1414
let cachedClient: OpenAI | null = null
1515

16-
export function getOpenAIClient(options?: {
16+
export async function getOpenAIClient(options?: {
1717
maxRetries?: number
1818
fetchOverride?: typeof fetch
1919
source?: string
20-
}): OpenAI {
20+
}): Promise<OpenAI> {
2121
if (cachedClient) return cachedClient
2222

23+
const { default: OpenAIClass } = await import('openai')
2324
const apiKey = process.env.OPENAI_API_KEY || ''
2425
const baseURL = process.env.OPENAI_BASE_URL
2526

26-
const client = new OpenAI({
27+
const client = new OpenAIClass({
2728
apiKey,
2829
...(baseURL && { baseURL }),
2930
maxRetries: options?.maxRetries ?? 0,

src/services/api/openai/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ export async function* queryModelOpenAI(
290290
const maxTokens = options.maxOutputTokensOverride ?? upperLimit
291291

292292
// 11. Get client
293-
const client = getOpenAIClient({
293+
const client = await getOpenAIClient({
294294
maxRetries: 0,
295295
fetchOverride: options.fetchOverride as unknown as typeof fetch,
296296
source: options.querySource,
@@ -432,7 +432,8 @@ export async function* queryModelOpenAI(
432432
}
433433
} catch (error) {
434434
const errorMessage = error instanceof Error ? error.message : String(error)
435-
logForDebugging(`[OpenAI] Error: ${errorMessage}`, { level: 'error' })
435+
const stack = error instanceof Error ? `\n${error.stack}` : ''
436+
logForDebugging(`[OpenAI] Error: ${errorMessage}${stack}`, { level: 'error' })
436437
yield createAssistantAPIErrorMessage({
437438
content: `API Error: ${errorMessage}`,
438439
apiError: 'api_error',

src/services/api/openai/modelMapping.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
22
* Default mapping from Anthropic model names to OpenAI model names.
33
* Used only when ANTHROPIC_DEFAULT_*_MODEL env vars are not set.
44
*/
5-
const DEFAULT_MODEL_MAP: Record<string, string> = {
6-
'claude-sonnet-4-20250514': 'gpt-4o',
7-
'claude-sonnet-4-5-20250929': 'gpt-4o',
8-
'claude-sonnet-4-6': 'gpt-4o',
9-
'claude-opus-4-20250514': 'o3',
10-
'claude-opus-4-1-20250805': 'o3',
11-
'claude-opus-4-5-20251101': 'o3',
12-
'claude-opus-4-6': 'o3',
13-
'claude-haiku-4-5-20251001': 'gpt-4o-mini',
14-
'claude-3-5-haiku-20241022': 'gpt-4o-mini',
15-
'claude-3-7-sonnet-20250219': 'gpt-4o',
16-
'claude-3-5-sonnet-20241022': 'gpt-4o',
5+
function getDefaultModelMap(): Record<string, string> {
6+
return {
7+
'claude-sonnet-4-20250514': 'gpt-4o',
8+
'claude-sonnet-4-5-20250929': 'gpt-4o',
9+
'claude-sonnet-4-6': 'gpt-4o',
10+
'claude-opus-4-20250514': 'o3',
11+
'claude-opus-4-1-20250805': 'o3',
12+
'claude-opus-4-5-20251101': 'o3',
13+
'claude-opus-4-6': 'o3',
14+
'claude-haiku-4-5-20251001': 'gpt-4o-mini',
15+
'claude-3-5-haiku-20241022': 'gpt-4o-mini',
16+
'claude-3-7-sonnet-20250219': 'gpt-4o',
17+
'claude-3-5-sonnet-20241022': 'gpt-4o',
18+
}
1719
}
1820

1921
/**
@@ -59,5 +61,5 @@ export function resolveOpenAIModel(anthropicModel: string): string {
5961
if (anthropicOverride) return anthropicOverride
6062
}
6163

62-
return DEFAULT_MODEL_MAP[cleanModel] ?? cleanModel
64+
return getDefaultModelMap()?.[cleanModel] ?? cleanModel
6365
}

src/utils/model/modelStrings.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,13 @@ export function getModelStrings(): ModelStrings {
143143
}
144144
return applyModelOverrides(ms)
145145
}
146-
146+
/**
147+
* Reset the modelStrings cache so it re-initializes with the current provider on next access.
148+
* Call this after switching providers (e.g. after /login).
149+
*/
150+
export function resetModelStrings(): void {
151+
setModelStringsState(null as unknown as ModelStrings)
152+
}
147153
/**
148154
* Ensure model strings are fully initialized.
149155
* For Bedrock users, this waits for the profile fetch to complete.

0 commit comments

Comments
 (0)