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)
|
||||
try {
|
||||
const { deleteServerSession } = await import('../server-agent');
|
||||
const { deleteServerSession } = await import('../agents/server-agent');
|
||||
await deleteServerSession(env.DB, telegramUserId);
|
||||
} catch (error) {
|
||||
provisionLogger.error('서버 세션 삭제 실패 (무시)', error as Error);
|
||||
@@ -1432,7 +1432,7 @@ export async function executeServerOrder(
|
||||
|
||||
// Clear server consultation session
|
||||
try {
|
||||
const { deleteServerSession } = await import('../server-agent');
|
||||
const { deleteServerSession } = await import('../agents/server-agent');
|
||||
await deleteServerSession(env.DB, telegramUserId);
|
||||
} catch (error) {
|
||||
provisionLogger.error('서버 세션 삭제 실패 (무시)', error as Error);
|
||||
|
||||
Reference in New Issue
Block a user