| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | 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); |
| | }; |
| | } |