아키텍처 개선: - 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>
184 lines
6.2 KiB
TypeScript
184 lines
6.2 KiB
TypeScript
import { executeDepositFunction, type DepositContext } from '../deposit-agent';
|
|
import type { Env } from '../types';
|
|
|
|
export const manageDepositTool = {
|
|
type: 'function',
|
|
function: {
|
|
name: 'manage_deposit',
|
|
description: '예치금을 관리합니다. "입금", "충전", "잔액", "계좌", "계좌번호", "송금", "거래내역" 등의 키워드가 포함되면 반드시 이 도구를 사용하세요.',
|
|
parameters: {
|
|
type: 'object',
|
|
properties: {
|
|
action: {
|
|
type: 'string',
|
|
enum: ['balance', 'account', 'request', 'history', 'cancel', 'pending', 'confirm', 'reject'],
|
|
description: 'balance: 잔액 조회, account: 입금 계좌 안내, request: 입금 신고(충전 요청), history: 거래 내역, cancel: 입금 취소, pending: 대기 목록(관리자), confirm: 입금 확인(관리자), reject: 입금 거절(관리자)',
|
|
},
|
|
depositor_name: {
|
|
type: 'string',
|
|
description: '입금자명. request action에서 필수',
|
|
},
|
|
amount: {
|
|
type: 'number',
|
|
description: '금액. request action에서 필수. 자연어 금액은 숫자로 변환 (만원→10000, 5천원→5000)',
|
|
},
|
|
transaction_id: {
|
|
type: 'number',
|
|
description: '거래 ID. cancel, confirm, reject action에서 필수',
|
|
},
|
|
limit: {
|
|
type: 'number',
|
|
description: '조회 개수. history action에서 사용 (기본 10)',
|
|
},
|
|
},
|
|
required: ['action'],
|
|
},
|
|
},
|
|
};
|
|
|
|
// 예치금 결과 포맷팅 (고정 형식)
|
|
function formatDepositResult(action: string, result: any): string {
|
|
if (result.error) {
|
|
return `🚫 ${result.error}`;
|
|
}
|
|
|
|
switch (action) {
|
|
case 'balance':
|
|
return `💰 현재 잔액: ${result.formatted}`;
|
|
|
|
case 'account':
|
|
return `💳 입금 계좌 안내
|
|
|
|
• 은행: ${result.bank}
|
|
• 계좌번호: ${result.account}
|
|
• 예금주: ${result.holder}
|
|
|
|
📌 ${result.instruction}`;
|
|
|
|
case 'request':
|
|
if (result.auto_matched) {
|
|
return `✅ 입금 확인 완료!
|
|
|
|
• 입금액: ${result.amount.toLocaleString()}원
|
|
• 입금자: ${result.depositor_name}
|
|
• 현재 잔액: ${result.new_balance.toLocaleString()}원
|
|
|
|
${result.message}`;
|
|
} else {
|
|
return `📋 입금 요청 등록 (#${result.transaction_id})
|
|
|
|
• 입금액: ${result.amount.toLocaleString()}원
|
|
• 입금자: ${result.depositor_name}
|
|
|
|
💳 입금 계좌
|
|
${result.account_info.bank} ${result.account_info.account}
|
|
(${result.account_info.holder})
|
|
|
|
📌 ${result.message}`;
|
|
}
|
|
|
|
case 'history': {
|
|
if (result.message && !result.transactions?.length) {
|
|
return `📋 ${result.message}`;
|
|
}
|
|
const statusIcon = (s: string) => s === 'confirmed' ? '✓' : s === 'pending' ? '⏳' : '✗';
|
|
const typeLabel = (t: string) => t === 'deposit' ? '입금' : t === 'withdrawal' ? '출금' : t === 'refund' ? '환불' : t;
|
|
const txList = result.transactions.map((tx: any) => {
|
|
const date = tx.confirmed_at || tx.created_at;
|
|
const dateStr = date ? new Date(date).toLocaleDateString('ko-KR', { month: '2-digit', day: '2-digit' }) : '';
|
|
const desc = tx.description ? ` - ${tx.description}` : '';
|
|
return `#${tx.id}: ${typeLabel(tx.type)} ${tx.amount.toLocaleString()}원 ${statusIcon(tx.status)} (${dateStr})${desc}`;
|
|
}).join('\n');
|
|
return `📋 거래 내역\n\n${txList}`;
|
|
}
|
|
|
|
case 'cancel':
|
|
return `✅ 거래 #${result.transaction_id} 취소 완료`;
|
|
|
|
case 'pending': {
|
|
if (result.message && !result.pending?.length) {
|
|
return `📋 ${result.message}`;
|
|
}
|
|
const pendingList = result.pending.map((p: any) =>
|
|
`#${p.id}: ${p.depositor_name} ${p.amount.toLocaleString()}원 (${p.user})`
|
|
).join('\n');
|
|
return `📋 대기 중인 입금 요청\n\n${pendingList}`;
|
|
}
|
|
|
|
case 'confirm':
|
|
return `✅ 입금 확인 완료 (#${result.transaction_id}, ${result.amount.toLocaleString()}원)`;
|
|
|
|
case 'reject':
|
|
return `❌ 입금 거절 완료 (#${result.transaction_id})`;
|
|
|
|
default:
|
|
return `💰 ${JSON.stringify(result)}`;
|
|
}
|
|
}
|
|
|
|
export async function executeManageDeposit(
|
|
args: { action: string; depositor_name?: string; amount?: number; transaction_id?: number; limit?: number },
|
|
env?: Env,
|
|
telegramUserId?: string,
|
|
db?: D1Database
|
|
): Promise<string> {
|
|
const { action, depositor_name, amount, transaction_id, limit } = args;
|
|
console.log('[manage_deposit] 시작:', { action, depositor_name, amount, telegramUserId });
|
|
|
|
if (!telegramUserId || !db) {
|
|
return '🚫 예치금 기능을 사용할 수 없습니다.';
|
|
}
|
|
|
|
// 사용자 조회
|
|
const user = await db.prepare(
|
|
'SELECT id FROM users WHERE telegram_id = ?'
|
|
).bind(telegramUserId).first<{ id: number }>();
|
|
|
|
if (!user) {
|
|
return '🚫 사용자 정보를 찾을 수 없습니다.';
|
|
}
|
|
|
|
const isAdmin = telegramUserId === env?.DEPOSIT_ADMIN_ID;
|
|
const context: DepositContext = {
|
|
userId: user.id,
|
|
telegramUserId,
|
|
isAdmin,
|
|
db,
|
|
};
|
|
|
|
// action → executeDepositFunction 매핑
|
|
const actionMap: Record<string, string> = {
|
|
balance: 'get_balance',
|
|
account: 'get_account_info',
|
|
request: 'request_deposit',
|
|
history: 'get_transactions',
|
|
cancel: 'cancel_transaction',
|
|
pending: 'get_pending_list',
|
|
confirm: 'confirm_deposit',
|
|
reject: 'reject_deposit',
|
|
};
|
|
|
|
const funcName = actionMap[action];
|
|
if (!funcName) {
|
|
return `🚫 알 수 없는 작업: ${action}`;
|
|
}
|
|
|
|
try {
|
|
const funcArgs: Record<string, any> = {};
|
|
if (depositor_name) funcArgs.depositor_name = depositor_name;
|
|
if (amount) funcArgs.amount = Number(amount);
|
|
if (transaction_id) funcArgs.transaction_id = Number(transaction_id);
|
|
if (limit) funcArgs.limit = Number(limit);
|
|
|
|
console.log('[manage_deposit] executeDepositFunction 호출:', funcName, funcArgs);
|
|
const result = await executeDepositFunction(funcName, funcArgs, context);
|
|
console.log('[manage_deposit] 결과:', JSON.stringify(result).slice(0, 200));
|
|
|
|
// 결과 포맷팅 (고정 형식)
|
|
return formatDepositResult(action, result);
|
|
} catch (error) {
|
|
console.error('[manage_deposit] 오류:', error);
|
|
return `🚫 예치금 처리 오류: ${String(error)}`;
|
|
}
|
|
}
|