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:
kappa
2026-02-05 09:48:39 +09:00
parent 676f3866e8
commit e463a88803
2 changed files with 214 additions and 2 deletions

212
src/agents/domain-agent.ts Normal file
View 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__';
}

View File

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