|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class AppError extends Error { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(message, statusCode = 500, type = 'server_error') { |
|
|
super(message); |
|
|
this.name = 'AppError'; |
|
|
this.statusCode = statusCode; |
|
|
this.type = type; |
|
|
this.isOperational = true; |
|
|
Error.captureStackTrace(this, this.constructor); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class UpstreamApiError extends AppError { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(message, statusCode, rawBody = null) { |
|
|
super(message, statusCode, 'upstream_api_error'); |
|
|
this.name = 'UpstreamApiError'; |
|
|
this.rawBody = rawBody; |
|
|
this.isUpstreamApiError = true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class AuthenticationError extends AppError { |
|
|
|
|
|
|
|
|
|
|
|
constructor(message = '认证失败') { |
|
|
super(message, 401, 'authentication_error'); |
|
|
this.name = 'AuthenticationError'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class AuthorizationError extends AppError { |
|
|
|
|
|
|
|
|
|
|
|
constructor(message = '无权限访问') { |
|
|
super(message, 403, 'authorization_error'); |
|
|
this.name = 'AuthorizationError'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class ValidationError extends AppError { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(message = '请求参数无效', details = null) { |
|
|
super(message, 400, 'validation_error'); |
|
|
this.name = 'ValidationError'; |
|
|
this.details = details; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class NotFoundError extends AppError { |
|
|
|
|
|
|
|
|
|
|
|
constructor(message = '资源未找到') { |
|
|
super(message, 404, 'not_found'); |
|
|
this.name = 'NotFoundError'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class RateLimitError extends AppError { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(message = '请求过于频繁', retryAfter = null) { |
|
|
super(message, 429, 'rate_limit_error'); |
|
|
this.name = 'RateLimitError'; |
|
|
this.retryAfter = retryAfter; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class TokenError extends AppError { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(message, tokenSuffix = null, statusCode = 500) { |
|
|
super(message, statusCode, 'token_error'); |
|
|
this.name = 'TokenError'; |
|
|
this.tokenSuffix = tokenSuffix; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function createApiError(message, status, rawBody) { |
|
|
return new UpstreamApiError(message, status, rawBody); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function extractErrorMessage(error) { |
|
|
if (error.isUpstreamApiError && error.rawBody) { |
|
|
try { |
|
|
const raw = typeof error.rawBody === 'string' ? JSON.parse(error.rawBody) : error.rawBody; |
|
|
return raw.error?.message || raw.message || error.message; |
|
|
} catch {} |
|
|
} |
|
|
return error.message || 'Internal server error'; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function buildOpenAIErrorPayload(error, statusCode) { |
|
|
|
|
|
if (error.isUpstreamApiError && error.rawBody) { |
|
|
try { |
|
|
const raw = typeof error.rawBody === 'string' ? JSON.parse(error.rawBody) : error.rawBody; |
|
|
const inner = raw.error || raw; |
|
|
return { |
|
|
error: { |
|
|
message: inner.message || error.message || 'Upstream API error', |
|
|
type: inner.type || 'upstream_api_error', |
|
|
code: inner.code ?? statusCode |
|
|
} |
|
|
}; |
|
|
} catch { |
|
|
return { |
|
|
error: { |
|
|
message: error.rawBody || error.message || 'Upstream API error', |
|
|
type: 'upstream_api_error', |
|
|
code: statusCode |
|
|
} |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (error instanceof AppError) { |
|
|
return { |
|
|
error: { |
|
|
message: error.message, |
|
|
type: error.type, |
|
|
code: error.statusCode |
|
|
} |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
return { |
|
|
error: { |
|
|
message: error.message || 'Internal server error', |
|
|
type: 'server_error', |
|
|
code: statusCode |
|
|
} |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function buildGeminiErrorPayload(error, statusCode) { |
|
|
return { |
|
|
error: { |
|
|
code: statusCode, |
|
|
message: extractErrorMessage(error), |
|
|
status: "INTERNAL" |
|
|
} |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function buildClaudeErrorPayload(error, statusCode) { |
|
|
const errorType = statusCode === 401 ? "authentication_error" : |
|
|
statusCode === 429 ? "rate_limit_error" : |
|
|
statusCode === 400 ? "invalid_request_error" : |
|
|
"api_error"; |
|
|
|
|
|
return { |
|
|
type: "error", |
|
|
error: { |
|
|
type: errorType, |
|
|
message: extractErrorMessage(error) |
|
|
} |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function errorHandler(err, req, res, next) { |
|
|
|
|
|
if (res.headersSent) { |
|
|
return next(err); |
|
|
} |
|
|
|
|
|
|
|
|
if (err.type === 'entity.too.large') { |
|
|
return res.status(413).json({ |
|
|
error: { |
|
|
message: '请求体过大', |
|
|
type: 'payload_too_large', |
|
|
code: 413 |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const statusCode = err.statusCode || err.status || 500; |
|
|
|
|
|
|
|
|
const errorPayload = buildOpenAIErrorPayload(err, statusCode); |
|
|
|
|
|
return res.status(statusCode).json(errorPayload); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function asyncHandler(fn) { |
|
|
return (req, res, next) => { |
|
|
Promise.resolve(fn(req, res, next)).catch(next); |
|
|
}; |
|
|
} |