refactor: 파일 분리 리팩토링 (routes, services, tools, utils)

아키텍처 개선:
- index.ts: 921줄 → 205줄 (77% 감소)
- openai-service.ts: 1,356줄 → 148줄 (89% 감소)

새로운 디렉토리 구조:
- src/routes/ - Webhook, API, Health check 핸들러
  - webhook.ts (287줄)
  - api.ts (318줄)
  - health.ts (14줄)

- src/services/ - 비즈니스 로직
  - bank-sms-parser.ts (143줄)
  - deposit-matcher.ts (88줄)

- src/tools/ - Function Calling 도구 모듈화
  - weather-tool.ts (37줄)
  - search-tool.ts (156줄)
  - domain-tool.ts (725줄)
  - deposit-tool.ts (183줄)
  - utility-tools.ts (60줄)
  - index.ts (104줄) - 도구 레지스트리

- src/utils/ - 유틸리티 함수
  - email-decoder.ts - Quoted-Printable 디코더

타입 에러 수정:
- routes/webhook.ts: text undefined 체크
- summary-service.ts: D1 타입 캐스팅
- summary-service.ts: Workers AI 타입 처리
- n8n-service.ts: Workers AI 타입 + 미사용 변수 제거

빌드 검증:
- TypeScript 타입 체크 통과
- Wrangler dev 로컬 빌드 성공

문서:
- REFACTORING_SUMMARY.md 추가
- ROUTE_ARCHITECTURE.md 추가

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-19 15:36:17 +09:00
parent 3bf42947a7
commit ab6c9a2efa
18 changed files with 2578 additions and 1958 deletions

104
src/tools/index.ts Normal file
View File

@@ -0,0 +1,104 @@
// Tool Registry - All tools exported from here
import { weatherTool, executeWeather } from './weather-tool';
import { searchWebTool, lookupDocsTool, executeSearchWeb, executeLookupDocs } from './search-tool';
import { manageDomainTool, suggestDomainsTool, executeManageDomain, executeSuggestDomains } from './domain-tool';
import { manageDepositTool, executeManageDeposit } from './deposit-tool';
import { getCurrentTimeTool, calculateTool, executeGetCurrentTime, executeCalculate } from './utility-tools';
import type { Env } from '../types';
// All tools array (used by OpenAI API)
export const tools = [
weatherTool,
searchWebTool,
getCurrentTimeTool,
calculateTool,
lookupDocsTool,
manageDomainTool,
manageDepositTool,
suggestDomainsTool,
];
// Tool categories for dynamic loading
export const TOOL_CATEGORIES: Record<string, string[]> = {
domain: ['manage_domain', 'suggest_domains'],
deposit: ['manage_deposit'],
weather: ['get_weather'],
search: ['search_web', 'lookup_docs'],
utility: ['get_current_time', 'calculate'],
};
// Category detection patterns
export const CATEGORY_PATTERNS: Record<string, RegExp> = {
domain: /도메인|네임서버|whois|dns|tld|등록|\.com|\.net|\.io|\.kr|\.org/i,
deposit: /입금|충전|잔액|계좌|예치금|송금|돈/i,
weather: /날씨|기온|비|눈|맑|흐림|더워|추워/i,
search: /검색|찾아|뭐야|뉴스|최신/i,
};
// Message-based tool selection
export function selectToolsForMessage(message: string): typeof tools {
const selectedCategories = new Set<string>(['utility']); // 항상 포함
for (const [category, pattern] of Object.entries(CATEGORY_PATTERNS)) {
if (pattern.test(message)) {
selectedCategories.add(category);
}
}
// 패턴 매칭 없으면 전체 도구 사용 (폴백)
if (selectedCategories.size === 1) {
console.log('[ToolSelector] 패턴 매칭 없음 → 전체 도구 사용');
return tools;
}
const selectedNames = new Set(
[...selectedCategories].flatMap(cat => TOOL_CATEGORIES[cat] || [])
);
const selectedTools = tools.filter(t => selectedNames.has(t.function.name));
console.log('[ToolSelector] 메시지:', message);
console.log('[ToolSelector] 카테고리:', [...selectedCategories].join(', '));
console.log('[ToolSelector] 선택된 도구:', selectedTools.map(t => t.function.name).join(', '));
return selectedTools;
}
// Tool execution dispatcher
export async function executeTool(
name: string,
args: Record<string, any>,
env?: Env,
telegramUserId?: string,
db?: D1Database
): Promise<string> {
switch (name) {
case 'get_weather':
return executeWeather(args as { city: string });
case 'search_web':
return executeSearchWeb(args as { query: string }, env);
case 'lookup_docs':
return executeLookupDocs(args as { library: string; query: string });
case 'get_current_time':
return executeGetCurrentTime(args as { timezone?: string });
case 'calculate':
return executeCalculate(args as { expression: string });
case 'manage_domain':
return executeManageDomain(args as { action: string; domain?: string; nameservers?: string[]; tld?: string }, env, telegramUserId, db);
case 'suggest_domains':
return executeSuggestDomains(args as { keywords: string }, env);
case 'manage_deposit':
return executeManageDeposit(args as { action: string; depositor_name?: string; amount?: number; transaction_id?: number; limit?: number }, env, telegramUserId, db);
default:
return `알 수 없는 도구: ${name}`;
}
}