@@ -195,71 +195,49 @@ export function getErrorMessage(code: ErrorCode, language: 'en' | 'zh' = 'en'):
195195
196196/**
197197 * 映射 Node.js 系统错误
198+ * 返回错误码和原始消息,不返回友好消息
198199 */
199- export function mapNodeError ( error : NodeJS . ErrnoException ) : AppError {
200+ export function mapNodeError ( error : NodeJS . ErrnoException ) : { code : ErrorCode ; originalMessage : string ; retryable : boolean } {
200201 const code = error . code || ''
202+ const originalMessage = error . message
201203
202204 switch ( code ) {
203205 case 'ENOENT' :
204- return new AppError (
205- error . message ,
206- ErrorCode . FILE_NOT_FOUND ,
207- false ,
208- error
209- )
206+ return { code : ErrorCode . FILE_NOT_FOUND , originalMessage, retryable : false }
210207
211208 case 'EACCES' :
212209 case 'EPERM' :
213- return new AppError (
214- error . message ,
215- ErrorCode . FILE_ACCESS_DENIED ,
216- false ,
217- error
218- )
210+ return { code : ErrorCode . FILE_ACCESS_DENIED , originalMessage, retryable : false }
219211
220212 case 'ETIMEDOUT' :
221213 case 'ESOCKETTIMEDOUT' :
222- return new AppError (
223- error . message ,
224- ErrorCode . TIMEOUT ,
225- true ,
226- error
227- )
214+ return { code : ErrorCode . TIMEOUT , originalMessage, retryable : true }
228215
229216 case 'ECONNREFUSED' :
230217 case 'ENOTFOUND' :
231218 case 'ENETUNREACH' :
232- return new AppError (
233- error . message ,
234- ErrorCode . NETWORK ,
235- true ,
236- error
237- )
219+ return { code : ErrorCode . NETWORK , originalMessage, retryable : true }
238220
239221 default :
240- return new AppError (
241- error . message || 'System error' ,
242- ErrorCode . UNKNOWN ,
243- false ,
244- error
245- )
222+ return { code : ErrorCode . UNKNOWN , originalMessage : originalMessage || 'System error' , retryable : false }
246223 }
247224}
248225
249226/**
250227 * 映射 AI SDK 错误(使用类型安全的 isInstance 方法)
228+ * 返回 ErrorCode 和原始错误消息(用于日志),不返回友好消息
251229 */
252- export function mapAISDKError ( error : unknown ) : { code : ErrorCode ; message : string ; retryable : boolean } {
230+ export function mapAISDKError ( error : unknown ) : { code : ErrorCode ; originalMessage : string ; retryable : boolean } {
253231 // 确保是 Error 对象
254232 if ( ! ( error instanceof Error ) ) {
255233 return {
256234 code : ErrorCode . UNKNOWN ,
257- message : String ( error ) ,
235+ originalMessage : String ( error ) ,
258236 retryable : false ,
259237 }
260238 }
261239
262- const errorMessage = error . message
240+ const originalMessage = error . message
263241
264242 // NoOutputGeneratedError - 通常包装了其他错误,优先提取 cause
265243 if ( NoOutputGeneratedError . isInstance ( error ) ) {
@@ -269,7 +247,7 @@ export function mapAISDKError(error: unknown): { code: ErrorCode; message: strin
269247 }
270248 return {
271249 code : ErrorCode . LLM_NO_OUTPUT ,
272- message : errorMessage ,
250+ originalMessage ,
273251 retryable : true ,
274252 }
275253 }
@@ -282,7 +260,7 @@ export function mapAISDKError(error: unknown): { code: ErrorCode; message: strin
282260 }
283261 return {
284262 code : ErrorCode . UNKNOWN ,
285- message : errorMessage ,
263+ originalMessage ,
286264 retryable : false ,
287265 }
288266 }
@@ -291,31 +269,48 @@ export function mapAISDKError(error: unknown): { code: ErrorCode; message: strin
291269 if ( NoContentGeneratedError . isInstance ( error ) ) {
292270 return {
293271 code : ErrorCode . LLM_NO_CONTENT ,
294- message : errorMessage ,
272+ originalMessage ,
295273 retryable : true ,
296274 }
297275 }
298276
299277 // APICallError - 根据状态码细分
300278 if ( APICallError . isInstance ( error ) ) {
301279 const statusCode = ( error as any ) . statusCode
280+ const responseBody = ( error as any ) . responseBody
281+
282+ // 尝试从 responseBody 提取详细信息
283+ let detailMessage = originalMessage
284+ if ( responseBody && typeof responseBody === 'string' ) {
285+ try {
286+ const body = JSON . parse ( responseBody )
287+ if ( body . detail ) {
288+ detailMessage = `${ originalMessage } : ${ body . detail } `
289+ } else if ( body . message ) {
290+ detailMessage = `${ originalMessage } : ${ body . message } `
291+ }
292+ } catch {
293+ // JSON 解析失败,使用原始消息
294+ }
295+ }
296+
302297 if ( statusCode === 429 ) {
303298 return {
304299 code : ErrorCode . API_RATE_LIMIT ,
305- message : errorMessage ,
300+ originalMessage : detailMessage ,
306301 retryable : true ,
307302 }
308303 }
309304 if ( statusCode === 401 || statusCode === 403 ) {
310305 return {
311306 code : ErrorCode . API_KEY_INVALID ,
312- message : errorMessage ,
307+ originalMessage : detailMessage ,
313308 retryable : false ,
314309 }
315310 }
316311 return {
317312 code : ErrorCode . API_CALL_FAILED ,
318- message : errorMessage ,
313+ originalMessage : detailMessage ,
319314 retryable : ( error as any ) . isRetryable ?? true ,
320315 }
321316 }
@@ -324,7 +319,7 @@ export function mapAISDKError(error: unknown): { code: ErrorCode; message: strin
324319 if ( InvalidPromptError . isInstance ( error ) ) {
325320 return {
326321 code : ErrorCode . LLM_INVALID_PROMPT ,
327- message : errorMessage ,
322+ originalMessage ,
328323 retryable : false ,
329324 }
330325 }
@@ -333,7 +328,7 @@ export function mapAISDKError(error: unknown): { code: ErrorCode; message: strin
333328 if ( InvalidResponseDataError . isInstance ( error ) ) {
334329 return {
335330 code : ErrorCode . LLM_INVALID_RESPONSE ,
336- message : errorMessage ,
331+ originalMessage ,
337332 retryable : true ,
338333 }
339334 }
@@ -342,7 +337,7 @@ export function mapAISDKError(error: unknown): { code: ErrorCode; message: strin
342337 if ( EmptyResponseBodyError . isInstance ( error ) ) {
343338 return {
344339 code : ErrorCode . LLM_EMPTY_RESPONSE ,
345- message : errorMessage ,
340+ originalMessage ,
346341 retryable : true ,
347342 }
348343 }
@@ -351,7 +346,7 @@ export function mapAISDKError(error: unknown): { code: ErrorCode; message: strin
351346 if ( LoadAPIKeyError . isInstance ( error ) ) {
352347 return {
353348 code : ErrorCode . API_KEY_INVALID ,
354- message : errorMessage ,
349+ originalMessage ,
355350 retryable : false ,
356351 }
357352 }
@@ -360,7 +355,7 @@ export function mapAISDKError(error: unknown): { code: ErrorCode; message: strin
360355 if ( NoSuchModelError . isInstance ( error ) ) {
361356 return {
362357 code : ErrorCode . LLM_NO_SUCH_MODEL ,
363- message : errorMessage ,
358+ originalMessage ,
364359 retryable : false ,
365360 }
366361 }
@@ -369,7 +364,7 @@ export function mapAISDKError(error: unknown): { code: ErrorCode; message: strin
369364 if ( TypeValidationError . isInstance ( error ) ) {
370365 return {
371366 code : ErrorCode . LLM_VALIDATION_FAILED ,
372- message : errorMessage ,
367+ originalMessage ,
373368 retryable : false ,
374369 }
375370 }
@@ -378,7 +373,7 @@ export function mapAISDKError(error: unknown): { code: ErrorCode; message: strin
378373 if ( UnsupportedFunctionalityError . isInstance ( error ) ) {
379374 return {
380375 code : ErrorCode . LLM_UNSUPPORTED ,
381- message : errorMessage ,
376+ originalMessage ,
382377 retryable : false ,
383378 }
384379 }
@@ -387,40 +382,41 @@ export function mapAISDKError(error: unknown): { code: ErrorCode; message: strin
387382 if ( error . name === 'AbortError' ) {
388383 return {
389384 code : ErrorCode . ABORTED ,
390- message : errorMessage ,
385+ originalMessage ,
391386 retryable : false ,
392387 }
393388 }
394389
395390 // 检查错误消息中的关键词(兜底)
396- const msg = errorMessage . toLowerCase ( )
391+ const msg = originalMessage . toLowerCase ( )
397392 if ( msg . includes ( 'network' ) || msg . includes ( 'fetch' ) || msg . includes ( 'econnrefused' ) ) {
398393 return {
399394 code : ErrorCode . NETWORK ,
400- message : errorMessage ,
395+ originalMessage ,
401396 retryable : true ,
402397 }
403398 }
404399 if ( msg . includes ( 'timeout' ) ) {
405400 return {
406401 code : ErrorCode . TIMEOUT ,
407- message : errorMessage ,
402+ originalMessage ,
408403 retryable : true ,
409404 }
410405 }
411406
412407 // 未知错误
413408 return {
414409 code : ErrorCode . UNKNOWN ,
415- message : errorMessage ,
410+ originalMessage ,
416411 retryable : false ,
417412 }
418413}
419414
420415/**
421416 * 将任意错误转换为 AppError
417+ * 使用英文友好消息(前端可根据用户语言转换)
422418 */
423- export function toAppError ( error : unknown ) : AppError {
419+ export function toAppError ( error : unknown , language : 'en' | 'zh' = 'en' ) : AppError {
424420 if ( error instanceof AppError ) {
425421 return error
426422 }
@@ -429,15 +425,21 @@ export function toAppError(error: unknown): AppError {
429425 // Node.js 系统错误
430426 const nodeError = error as NodeJS . ErrnoException
431427 if ( nodeError . code ) {
432- return mapNodeError ( nodeError )
428+ const mapped = mapNodeError ( nodeError )
429+ const friendlyMessage = getErrorMessage ( mapped . code , language )
430+ return new AppError ( friendlyMessage , mapped . code , mapped . retryable , error )
433431 }
434432
435- return new AppError ( error . message , ErrorCode . UNKNOWN , false , error )
433+ // 普通 Error
434+ const friendlyMessage = getErrorMessage ( ErrorCode . UNKNOWN , language )
435+ return new AppError ( friendlyMessage , ErrorCode . UNKNOWN , false , error )
436436 }
437437
438438 if ( typeof error === 'string' ) {
439- return new AppError ( error , ErrorCode . UNKNOWN , false )
439+ const friendlyMessage = getErrorMessage ( ErrorCode . UNKNOWN , language )
440+ return new AppError ( friendlyMessage , ErrorCode . UNKNOWN , false )
440441 }
441442
442- return new AppError ( 'An unexpected error occurred' , ErrorCode . UNKNOWN , false , error )
443+ const friendlyMessage = getErrorMessage ( ErrorCode . UNKNOWN , language )
444+ return new AppError ( friendlyMessage , ErrorCode . UNKNOWN , false , error )
443445}
0 commit comments