refactor(errorHandling): improve error serialization and update error handling in callbacks

- Updated the error handling in the `onError` callback to use `AISDKError` type for better type safety.
- Introduced a new `serializeError` function to standardize error serialization.
- Modified the `ErrorBlock` component to directly access the error message.
- Removed deprecated error formatting functions to streamline the error utility module.
This commit is contained in:
suyao 2025-08-29 06:12:16 +08:00
parent a30df46c40
commit fee6ad58d1
No known key found for this signature in database
5 changed files with 29 additions and 109 deletions

View File

@ -32,7 +32,7 @@ const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock; message: Message }>
return (
<Alert
description={getHttpMessageLabel(block.error.status)}
message={block.error?.message}
message={block.error.message}
type="error"
closable
onClose={onRemoveBlock}

View File

@ -15,10 +15,11 @@ import {
PlaceholderMessageBlock
} from '@renderer/types/newMessage'
import { uuid } from '@renderer/utils'
import { formatErrorMessage, isAbortError } from '@renderer/utils/error'
import { isAbortError, serializeError } from '@renderer/utils/error'
import { createBaseMessageBlock, createErrorBlock } from '@renderer/utils/messageUtils/create'
import { findAllBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
import { isFocused, isOnHomePage } from '@renderer/utils/window'
import { AISDKError } from 'ai'
import { BlockManager } from '../BlockManager'
@ -79,21 +80,12 @@ export const createBaseCallbacks = (deps: BaseCallbacksDependencies) => {
// await blockManager.handleBlockTransition(baseBlock as PlaceholderMessageBlock, MessageBlockType.UNKNOWN)
// },
onError: async (error: any) => {
onError: async (error: AISDKError) => {
logger.debug('onError', error)
const isErrorTypeAbort = isAbortError(error)
let pauseErrorLanguagePlaceholder = ''
const serializableError = serializeError(error)
if (isErrorTypeAbort) {
pauseErrorLanguagePlaceholder = 'pause_placeholder'
}
const serializableError = {
name: error.name,
message: pauseErrorLanguagePlaceholder || error.message || formatErrorMessage(error),
originalMessage: error.message,
stack: error.stack,
status: error.status || error.code,
requestId: error.request_id
serializableError.message = 'pause_placeholder'
}
const duration = Date.now() - startTime

View File

@ -50,7 +50,7 @@ export interface BaseMessageBlock {
status: MessageBlockStatus // 块状态
model?: Model // 使用的模型
metadata?: Record<string, any> // 通用元数据
error?: Record<string, any> // Added optional error field to base
error?: Record<string, any> // Serializable error object instead of AISDKError
}
export interface PlaceholderMessageBlock extends BaseMessageBlock {

View File

@ -1,6 +1,6 @@
import { describe, expect, it, vi } from 'vitest'
import { formatErrorMessage, formatMessageError, getErrorDetails, getErrorMessage, isAbortError } from '../error'
import { formatErrorMessage, getErrorDetails, isAbortError } from '../error'
describe('error', () => {
describe('getErrorDetails', () => {
@ -124,79 +124,6 @@ describe('error', () => {
})
})
describe('formatMessageError', () => {
it('should return error details as an object', () => {
const error = new Error('Test error')
const result = formatMessageError(error)
expect(result.message).toBe('Test error')
expect(result.stack).toBeUndefined()
expect(result.headers).toBeUndefined()
expect(result.request_id).toBeUndefined()
})
it('should handle string errors', () => {
const result = formatMessageError('String error')
expect(typeof result).toBe('string')
expect(result).toBe('String error')
})
it('should handle formatting errors', () => {
const problematicError = {
get message() {
throw new Error('Cannot access')
},
toString: () => 'Error object'
}
const result = formatMessageError(problematicError)
expect(result).toBeTruthy()
})
it('should handle completely invalid errors', () => {
let invalidError: any
try {
invalidError = Object.create(null)
Object.defineProperty(invalidError, 'toString', {
get: () => {
throw new Error()
}
})
} catch (e) {
invalidError = {
toString() {
throw new Error()
}
}
}
const result = formatMessageError(invalidError)
expect(result).toBeTruthy()
})
})
describe('getErrorMessage', () => {
it('should extract message from Error objects', () => {
const error = new Error('Test message')
expect(getErrorMessage(error)).toBe('Test message')
})
it('should handle objects with message property', () => {
const errorObj = { message: 'Object message' }
expect(getErrorMessage(errorObj)).toBe('Object message')
})
it('should convert non-Error objects to string', () => {
const obj = { toString: () => 'Custom toString' }
expect(getErrorMessage(obj)).toBe('Custom toString')
})
it('should return empty string for undefined or null', () => {
expect(getErrorMessage(undefined)).toBe('')
expect(getErrorMessage(null)).toBe('')
})
})
describe('isAbortError', () => {
it('should identify OpenAI abort errors by message', () => {
const openaiError = { message: 'Request was aborted.' }

View File

@ -1,4 +1,5 @@
import { loggerService } from '@logger'
import { AISDKError, APICallError } from 'ai'
import { t } from 'i18next'
const logger = loggerService.withContext('Utils:error')
@ -53,26 +54,6 @@ export function formatErrorMessage(error: any): string {
}
}
export function formatMessageError(error: any): Record<string, any> {
try {
const detailedError = getErrorDetails(error)
delete detailedError?.headers
delete detailedError?.stack
delete detailedError?.request_id
return detailedError
} catch (e) {
try {
return { message: String(error) }
} catch {
return { message: 'Error: Unable to format error message' }
}
}
}
export function getErrorMessage(error: any): string {
return error?.message || error?.toString() || ''
}
export const isAbortError = (error: any): boolean => {
// Convert message to string for consistent checking
const errorMessage = String(error?.message || '')
@ -106,3 +87,23 @@ export const formatMcpError = (error: any) => {
}
return error.message
}
export const serializeError = (error: AISDKError) => {
const baseError = {
name: error.name,
message: error.message,
stack: error.stack,
cause: error.cause ? String(error.cause) : undefined
}
if (APICallError.isInstance(error)) {
let content = error.message === '' ? error.responseBody || 'Unknown error' : error.message
try {
const obj = JSON.parse(content)
content = obj.error.message
} catch (e: any) {
logger.warn('Error parsing error response body:', e)
}
return { ...baseError, status: error.statusCode, url: error.url, message: content }
}
return baseError
}