feat: Intelligent Resilience & Trust Reporting (v2.77.2)

This commit is contained in:
g1nation
2026-05-05 17:04:27 +09:00
parent 037eafa02b
commit cf10d14148
29 changed files with 490 additions and 166 deletions
+69
View File
@@ -0,0 +1,69 @@
import { logInfo, logError } from '../utils';
export enum ApiErrorType {
AUTH_FAILURE = 'AUTH_FAILURE',
RATE_LIMIT = 'RATE_LIMIT',
NETWORK_TIMEOUT = 'NETWORK_TIMEOUT',
SERVER_ERROR = 'SERVER_ERROR',
UNKNOWN = 'UNKNOWN'
}
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: {
type: ApiErrorType;
message: string;
isRecoverable: boolean;
};
}
/**
* ExternalApiHandler: 외부 서비스(Ollama, Gemini, Search API 등)와의
* 모든 통신을 중앙 집중적으로 관리하며, 인증 복구 및 속도 제한 대응을 담당합니다.
*/
export class ExternalApiHandler {
/**
* 인증 오류 발생 시 복구 가능성을 판단하고 재시도 로직을 제어합니다.
*/
public static async handleAuthRecovery(error: any): Promise<boolean> {
logInfo(`[ApiHandler] 인증 오류 감지. 복구 시도 중...`);
// TODO: 실제 프로젝트의 인증 스키마(API Key, OAuth 등)에 따른 복구 로직 구현
// 예: 환경 변수 재로드, 만료된 토큰 갱신 요청 등
return false; // 현재는 기본적으로 수동 개입 필요로 처리
}
/**
* 지능형 요청 래퍼 (Resilient Fetch)
*/
public static async request<T>(
call: () => Promise<T>,
context: string
): Promise<ApiResponse<T>> {
try {
const data = await call();
return { success: true, data };
} catch (error: any) {
logError(`[ApiHandler] [${context}] 호출 실패:`, error);
const errorType = this.classifyError(error);
return {
success: false,
error: {
type: errorType,
message: error.message,
isRecoverable: errorType === ApiErrorType.NETWORK_TIMEOUT || errorType === ApiErrorType.RATE_LIMIT
}
};
}
}
private static classifyError(error: any): ApiErrorType {
const msg = error.message || '';
if (msg.includes('401') || msg.includes('unauthorized')) return ApiErrorType.AUTH_FAILURE;
if (msg.includes('429') || msg.includes('rate limit')) return ApiErrorType.RATE_LIMIT;
if (msg.includes('timeout') || msg.includes('ETIMEDOUT')) return ApiErrorType.NETWORK_TIMEOUT;
if (msg.includes('500') || msg.includes('502') || msg.includes('503')) return ApiErrorType.SERVER_ERROR;
return ApiErrorType.UNKNOWN;
}
}