feat: create domain-agent basic structure
- Add D1 session CRUD functions (getDomainSession, saveDomainSession, deleteDomainSession) - Add session helper functions (createDomainSession, isSessionExpired, addMessageToSession) - Add placeholder for main consultation handler (processDomainConsultation) - Fix server-agent import paths in server-tool.ts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
212
src/agents/domain-agent.ts
Normal file
212
src/agents/domain-agent.ts
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* Domain Agent - 도메인 추천 상담 시스템
|
||||||
|
*
|
||||||
|
* 기능:
|
||||||
|
* - 대화형 도메인 추천 상담
|
||||||
|
* - 세션 기반 정보 수집 (키워드, 용도, 예산)
|
||||||
|
* - 충분한 정보 수집 시 자동 추천
|
||||||
|
* - 추천 후 사용자 선택 및 등록 흐름
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Env, DomainSession, DomainSessionStatus } from '../types';
|
||||||
|
import { createLogger } from '../utils/logger';
|
||||||
|
|
||||||
|
const logger = createLogger('domain-agent');
|
||||||
|
|
||||||
|
// D1 Session Management
|
||||||
|
const DOMAIN_SESSION_TTL = 60 * 60 * 1000; // 1시간 (도메인 작업은 시간이 더 필요)
|
||||||
|
const MAX_MESSAGES = 20; // 세션당 최대 메시지 수
|
||||||
|
|
||||||
|
/**
|
||||||
|
* D1에서 도메인 세션 조회
|
||||||
|
*
|
||||||
|
* @param db - D1 Database
|
||||||
|
* @param userId - Telegram User ID
|
||||||
|
* @returns DomainSession 또는 null (세션 없거나 만료)
|
||||||
|
*/
|
||||||
|
export async function getDomainSession(
|
||||||
|
db: D1Database,
|
||||||
|
userId: string
|
||||||
|
): Promise<DomainSession | null> {
|
||||||
|
try {
|
||||||
|
const now = Date.now();
|
||||||
|
const result = await db.prepare(
|
||||||
|
'SELECT * FROM domain_sessions WHERE user_id = ? AND expires_at > ?'
|
||||||
|
).bind(userId, now).first<{
|
||||||
|
user_id: string;
|
||||||
|
status: string;
|
||||||
|
collected_info: string | null;
|
||||||
|
target_domain: string | null;
|
||||||
|
messages: string | null;
|
||||||
|
created_at: number;
|
||||||
|
updated_at: number;
|
||||||
|
expires_at: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
logger.info('도메인 세션 없음', { userId });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const session: DomainSession = {
|
||||||
|
user_id: result.user_id,
|
||||||
|
status: result.status as DomainSessionStatus,
|
||||||
|
collected_info: result.collected_info ? JSON.parse(result.collected_info) : {},
|
||||||
|
target_domain: result.target_domain || undefined,
|
||||||
|
messages: result.messages ? JSON.parse(result.messages) : [],
|
||||||
|
created_at: result.created_at,
|
||||||
|
updated_at: result.updated_at,
|
||||||
|
expires_at: result.expires_at,
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info('도메인 세션 조회 성공', { userId, status: session.status });
|
||||||
|
return session;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('도메인 세션 조회 실패', error as Error, { userId });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 도메인 세션 저장 (생성 또는 업데이트)
|
||||||
|
*
|
||||||
|
* @param db - D1 Database
|
||||||
|
* @param session - DomainSession
|
||||||
|
*/
|
||||||
|
export async function saveDomainSession(
|
||||||
|
db: D1Database,
|
||||||
|
session: DomainSession
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const now = Date.now();
|
||||||
|
const expiresAt = now + DOMAIN_SESSION_TTL;
|
||||||
|
|
||||||
|
await db.prepare(`
|
||||||
|
INSERT INTO domain_sessions
|
||||||
|
(user_id, status, collected_info, target_domain, messages, created_at, updated_at, expires_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT(user_id) DO UPDATE SET
|
||||||
|
status = excluded.status,
|
||||||
|
collected_info = excluded.collected_info,
|
||||||
|
target_domain = excluded.target_domain,
|
||||||
|
messages = excluded.messages,
|
||||||
|
updated_at = excluded.updated_at,
|
||||||
|
expires_at = excluded.expires_at
|
||||||
|
`).bind(
|
||||||
|
session.user_id,
|
||||||
|
session.status,
|
||||||
|
JSON.stringify(session.collected_info || {}),
|
||||||
|
session.target_domain || null,
|
||||||
|
JSON.stringify(session.messages || []),
|
||||||
|
session.created_at || now,
|
||||||
|
now,
|
||||||
|
expiresAt
|
||||||
|
).run();
|
||||||
|
|
||||||
|
logger.info('도메인 세션 저장 성공', { userId: session.user_id, status: session.status });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('도메인 세션 저장 실패', error as Error, { userId: session.user_id });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 도메인 세션 삭제
|
||||||
|
*
|
||||||
|
* @param db - D1 Database
|
||||||
|
* @param userId - Telegram User ID
|
||||||
|
*/
|
||||||
|
export async function deleteDomainSession(
|
||||||
|
db: D1Database,
|
||||||
|
userId: string
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
await db.prepare('DELETE FROM domain_sessions WHERE user_id = ?')
|
||||||
|
.bind(userId)
|
||||||
|
.run();
|
||||||
|
logger.info('도메인 세션 삭제 성공', { userId });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('도메인 세션 삭제 실패', error as Error, { userId });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 새 도메인 세션 생성
|
||||||
|
*
|
||||||
|
* @param userId - Telegram User ID
|
||||||
|
* @param status - 세션 상태
|
||||||
|
* @returns 새로운 DomainSession 객체
|
||||||
|
*/
|
||||||
|
export function createDomainSession(
|
||||||
|
userId: string,
|
||||||
|
status: DomainSessionStatus = 'gathering'
|
||||||
|
): DomainSession {
|
||||||
|
const now = Date.now();
|
||||||
|
return {
|
||||||
|
user_id: userId,
|
||||||
|
status,
|
||||||
|
collected_info: {},
|
||||||
|
messages: [],
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
expires_at: now + DOMAIN_SESSION_TTL,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 세션 만료 여부 확인
|
||||||
|
*
|
||||||
|
* @param session - DomainSession
|
||||||
|
* @returns true if expired, false otherwise
|
||||||
|
*/
|
||||||
|
export function isSessionExpired(session: DomainSession): boolean {
|
||||||
|
return session.expires_at < Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 세션에 메시지 추가
|
||||||
|
*
|
||||||
|
* @param session - DomainSession
|
||||||
|
* @param role - 메시지 역할 ('user' | 'assistant')
|
||||||
|
* @param content - 메시지 내용
|
||||||
|
*/
|
||||||
|
export function addMessageToSession(
|
||||||
|
session: DomainSession,
|
||||||
|
role: 'user' | 'assistant',
|
||||||
|
content: string
|
||||||
|
): void {
|
||||||
|
session.messages.push({ role, content });
|
||||||
|
|
||||||
|
// 최대 메시지 수 제한
|
||||||
|
if (session.messages.length > MAX_MESSAGES) {
|
||||||
|
session.messages = session.messages.slice(-MAX_MESSAGES);
|
||||||
|
logger.warn('세션 메시지 최대 개수 초과, 오래된 메시지 제거', {
|
||||||
|
userId: session.user_id,
|
||||||
|
maxMessages: MAX_MESSAGES,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 도메인 추천 상담 처리 (메인 함수)
|
||||||
|
*
|
||||||
|
* @param db - D1 Database
|
||||||
|
* @param userId - Telegram User ID
|
||||||
|
* @param userMessage - 사용자 메시지
|
||||||
|
* @param env - Environment
|
||||||
|
* @returns AI 응답 메시지
|
||||||
|
*/
|
||||||
|
export async function processDomainConsultation(
|
||||||
|
db: D1Database,
|
||||||
|
userId: string,
|
||||||
|
userMessage: string,
|
||||||
|
env: Env
|
||||||
|
): Promise<string> {
|
||||||
|
// TODO: Implement in Task 8
|
||||||
|
logger.info('도메인 상담 처리 요청 (미구현)', {
|
||||||
|
userId,
|
||||||
|
message: userMessage.slice(0, 50),
|
||||||
|
});
|
||||||
|
return '__PASSTHROUGH__';
|
||||||
|
}
|
||||||
@@ -1343,7 +1343,7 @@ export async function executeServerDelete(
|
|||||||
|
|
||||||
// Clear server consultation session (if any)
|
// Clear server consultation session (if any)
|
||||||
try {
|
try {
|
||||||
const { deleteServerSession } = await import('../server-agent');
|
const { deleteServerSession } = await import('../agents/server-agent');
|
||||||
await deleteServerSession(env.DB, telegramUserId);
|
await deleteServerSession(env.DB, telegramUserId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
provisionLogger.error('서버 세션 삭제 실패 (무시)', error as Error);
|
provisionLogger.error('서버 세션 삭제 실패 (무시)', error as Error);
|
||||||
@@ -1432,7 +1432,7 @@ export async function executeServerOrder(
|
|||||||
|
|
||||||
// Clear server consultation session
|
// Clear server consultation session
|
||||||
try {
|
try {
|
||||||
const { deleteServerSession } = await import('../server-agent');
|
const { deleteServerSession } = await import('../agents/server-agent');
|
||||||
await deleteServerSession(env.DB, telegramUserId);
|
await deleteServerSession(env.DB, telegramUserId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
provisionLogger.error('서버 세션 삭제 실패 (무시)', error as Error);
|
provisionLogger.error('서버 세션 삭제 실패 (무시)', error as Error);
|
||||||
|
|||||||
Reference in New Issue
Block a user