From a2d05c82c34d5acbc697a96fa42e382f781a1ab8 Mon Sep 17 00:00:00 2001 From: kappa Date: Mon, 26 Jan 2026 14:15:15 +0900 Subject: [PATCH] feat: add Server Expert AI with search/docs tools for trend-aware recommendations - Add server-agent.ts with 30-year senior architect persona - Implement KV-based session management for multi-turn conversations - Add search_trends (Brave Search) and lookup_framework_docs (Context7) tools - Function Calling support with max 3 tool calls per request - Auto-infer tech stack and expected users from use case/scale - Prohibit competitor provider mentions (AWS, GCP, Azure, etc.) - Simplify main AI system prompt, delegate complex logic to expert AI Co-Authored-By: Claude Opus 4.5 --- src/openai-service.ts | 19 ++ src/server-agent.ts | 457 +++++++++++++++++++++++++++++++++++++++ src/summary-service.ts | 4 +- src/tools/index.ts | 4 +- src/tools/server-tool.ts | 93 +++++++- src/types.ts | 21 +- 6 files changed, 587 insertions(+), 11 deletions(-) create mode 100644 src/server-agent.ts diff --git a/src/openai-service.ts b/src/openai-service.ts index 5405fd3..8729d35 100644 --- a/src/openai-service.ts +++ b/src/openai-service.ts @@ -93,6 +93,25 @@ export async function generateOpenAIResponse( telegramUserId?: string, db?: D1Database ): Promise { + // Check if server consultation session is active + if (telegramUserId && env.SESSION_KV) { + try { + const { getServerSession, processServerConsultation } = await import('./server-agent'); + const session = await getServerSession(env.SESSION_KV, telegramUserId); + + if (session && session.status !== 'completed') { + logger.info('Active server session detected, routing to consultation', { + userId: telegramUserId, + status: session.status + }); + return await processServerConsultation(userMessage, session, env); + } + } catch (error) { + logger.error('Session check failed, continuing with normal flow', error as Error); + // Continue with normal flow if session check fails + } + } + if (!env.OPENAI_API_KEY) { throw new Error('OPENAI_API_KEY not configured'); } diff --git a/src/server-agent.ts b/src/server-agent.ts new file mode 100644 index 0000000..bd58dc4 --- /dev/null +++ b/src/server-agent.ts @@ -0,0 +1,457 @@ +/** + * Server Expert Agent - 서버 전문가 AI 상담 시스템 + * + * 기능: + * - 대화형 서버 추천 상담 + * - 세션 기반 정보 수집 + * - 충분한 정보 수집 시 자동 추천 + * - Brave Search / Context7 도구로 최신 트렌드 반영 + */ + +import type { Env, ServerSession } from './types'; +import { createLogger } from './utils/logger'; +import { executeSearchWeb, executeLookupDocs } from './tools/search-tool'; + +const logger = createLogger('server-agent'); + +// KV Session Management +const SESSION_TTL = 3600; // 1 hour +const SESSION_KEY_PREFIX = 'server_session:'; + +export async function getServerSession( + kv: KVNamespace, + userId: string +): Promise { + try { + const key = `${SESSION_KEY_PREFIX}${userId}`; + const data = await kv.get(key, 'json'); + + if (!data) { + logger.info('세션 없음', { userId }); + return null; + } + + logger.info('세션 조회 성공', { userId, status: (data as ServerSession).status }); + return data as ServerSession; + } catch (error) { + logger.error('세션 조회 실패', error as Error, { userId }); + return null; + } +} + +export async function saveServerSession( + kv: KVNamespace, + userId: string, + session: ServerSession +): Promise { + try { + const key = `${SESSION_KEY_PREFIX}${userId}`; + session.updatedAt = Date.now(); + + await kv.put(key, JSON.stringify(session), { + expirationTtl: SESSION_TTL, + }); + + logger.info('세션 저장 성공', { userId, status: session.status }); + } catch (error) { + logger.error('세션 저장 실패', error as Error, { userId }); + throw error; + } +} + +export async function deleteServerSession( + kv: KVNamespace, + userId: string +): Promise { + try { + const key = `${SESSION_KEY_PREFIX}${userId}`; + await kv.delete(key); + logger.info('세션 삭제 성공', { userId }); + } catch (error) { + logger.error('세션 삭제 실패', error as Error, { userId }); + throw error; + } +} + +// Server Expert AI Tools +const serverExpertTools = [ + { + type: 'function' as const, + function: { + name: 'search_trends', + description: '최신 기술 트렌드, 서버 요구사항, 프레임워크 인기도를 검색합니다. 예: "2024 WordPress server requirements", "Next.js hosting best practices"', + parameters: { + type: 'object', + properties: { + query: { + type: 'string', + description: '검색 쿼리 (영문 권장, 기술 키워드 포함)', + }, + }, + required: ['query'], + }, + }, + }, + { + type: 'function' as const, + function: { + name: 'lookup_framework_docs', + description: '프레임워크/라이브러리 공식 문서에서 서버 요구사항, 배포 가이드, 권장 환경을 조회합니다.', + parameters: { + type: 'object', + properties: { + library: { + type: 'string', + description: '라이브러리/프레임워크 이름 (예: nextjs, laravel, django, wordpress)', + }, + topic: { + type: 'string', + description: '조회할 주제 (예: deployment requirements, production setup, server specs)', + }, + }, + required: ['library', 'topic'], + }, + }, + }, +]; + +// Execute server expert tool +async function executeServerExpertTool( + toolName: string, + args: Record, + env: Env +): Promise { + logger.info('도구 실행', { toolName, args }); + + switch (toolName) { + case 'search_trends': { + const result = await executeSearchWeb({ query: args.query as string }, env); + return result; + } + case 'lookup_framework_docs': { + const result = await executeLookupDocs({ + library: args.library as string, + query: args.topic as string, + }, env); + return result; + } + default: + return `알 수 없는 도구: ${toolName}`; + } +} + +// Tech stack inference from use case +function inferTechStack(useCase: string): string[] { + const useCaseLower = useCase.toLowerCase(); + + if (/블로그|blog|wordpress/.test(useCaseLower)) { + return ['wordpress']; + } + if (/쇼핑몰|이커머스|ecommerce|shop|store/.test(useCaseLower)) { + return ['ecommerce']; + } + if (/커뮤니티|게시판|forum|community/.test(useCaseLower)) { + return ['php', 'mysql']; + } + if (/api|백엔드|backend/.test(useCaseLower)) { + return ['nodejs', 'express']; + } + + return ['web']; // Default +} + +// Expected users inference from scale +function inferExpectedUsers(scale: string): number { + if (scale === 'personal') return 100; + if (scale === 'business') return 500; + return 100; // Default to personal +} + +// OpenAI API 응답 타입 +interface OpenAIToolCall { + id: string; + type: 'function'; + function: { + name: string; + arguments: string; + }; +} + +interface OpenAIMessage { + role: 'assistant'; + content: string | null; + tool_calls?: OpenAIToolCall[]; +} + +interface OpenAIAPIResponse { + choices: Array<{ + message: OpenAIMessage; + finish_reason: string; + }>; +} + +// OpenAI 호출 (서버 전문가 AI with Function Calling) +async function callServerExpertAI( + env: Env, + session: ServerSession, + userMessage: string +): Promise<{ action: 'question' | 'recommend'; message: string; collectedInfo: ServerSession['collectedInfo'] }> { + if (!env.OPENAI_API_KEY) { + throw new Error('OPENAI_API_KEY not configured'); + } + + const { getOpenAIUrl } = await import('./utils/api-urls'); + + // Build conversation history + const conversationHistory = session.messages.map(m => ({ + role: m.role === 'user' ? 'user' as const : 'assistant' as const, + content: m.content, + })); + + const systemPrompt = `당신은 30년 경력의 시니어 클라우드 아키텍트입니다. + +## 전문성 (30년 경력) +- 서버 엔지니어: Linux, Windows Server, 가상화, 컨테이너 마스터 +- 네트워크 엔지니어: 로드밸런싱, CDN, DNS, 보안 설계 전문 +- 클라우드 아키텍트: 모든 클라우드 플랫폼 경험 +- 수천 개의 서버 구축 경험으로 용도만 들으면 최적 스펙을 바로 판단 가능 + +## 성격 +- 따뜻하고 친근하지만 전문적인 어조 +- 비기술자도 이해하기 쉽게 설명 +- 고객의 예산과 상황을 항상 배려 +- 불필요한 기술 용어 사용 자제 + +## 금지 사항 (절대 위반 금지) +- AWS, GCP, Azure, Vultr, Linode, DigitalOcean 등 다른 클라우드 프로바이더 언급 금지 +- 경쟁사 서비스 추천 금지 +- 우리 서비스(Anvil)만 추천 +- "다른 곳도 고려해보세요" 같은 멘트 금지 + +## 도구 사용 가이드 (적극적으로 활용할 것) +- 고객이 특정 프레임워크/기술을 언급하면 (예: Next.js, Laravel, Django, Astro, Bun, Rust 등) → 반드시 lookup_framework_docs 호출하여 최신 공식 권장 스펙 확인 +- "최신", "트렌드", "2024", "2025", "요즘" 등 시의성 있는 키워드 → 반드시 search_trends 호출 +- 블로그, 쇼핑몰 같은 일반적 용도는 경험으로 바로 답변 +- 도구 결과를 자연스럽게 메시지에 포함 (예: "공식 문서에 따르면...") + +## 대화 흐름 +1. 용도 파악: "어떤 서비스를 운영하실 건가요? (예: 블로그, 쇼핑몰, 커뮤니티)" +2. 규모 파악: "개인용인가요, 사업용인가요?" +3. 정보가 충분하면 즉시 추천 (추가 질문 없이) + +## 핵심 규칙 (반드시 준수) +- 기술 스택, 동시접속자 수, 트래픽 패턴은 절대 묻지 않음 (30년 경험으로 알아서 추론) +- "모르겠어요", "아무거나", "글쎄요" → 즉시 action="recommend" (기본값: 개인용 웹서비스) +- 용도+규모 한번에 말하면 → 즉시 action="recommend" +- 용도만 말해도 → 개인용으로 가정하고 action="recommend" 가능 +- 질문은 최대 2번까지, 그 이후는 무조건 action="recommend" + +## 추론 규칙 (30년 경험 기반) +- 블로그 → WordPress, 1GB RAM이면 충분 +- 쇼핑몰 → 2GB+ RAM, DB 분리 고려 +- 커뮤니티 → PHP+MySQL, 트래픽에 따라 2~4GB +- 게임서버 → 고사양 CPU, 낮은 레이턴시 리전 +- 규모: personal→100명, business→500명 + +## 현재 수집된 정보 +${JSON.stringify(session.collectedInfo, null, 2)} + +## 응답 형식 (반드시 JSON만 반환, 다른 텍스트 절대 금지) +{ + "action": "question" | "recommend", + "message": "사용자에게 보여줄 메시지 (도구에서 얻은 정보를 자연스럽게 포함)", + "collectedInfo": { + "useCase": "용도 (없으면 '웹서비스')", + "scale": "personal 또는 business (없으면 'personal')" + } +} + +중요: 정보가 부족해도 기본값으로 action="recommend" 하세요. 30년 경험이면 충분합니다.`; + + try { + // Messages array that we'll build up with tool results + const messages: Array<{ role: string; content: string | null; tool_calls?: OpenAIToolCall[]; tool_call_id?: string; name?: string }> = [ + { role: 'system', content: systemPrompt }, + ...conversationHistory, + { role: 'user', content: userMessage }, + ]; + + const MAX_TOOL_CALLS = 3; + let toolCallCount = 0; + + // Loop to handle tool calls + while (toolCallCount < MAX_TOOL_CALLS) { + const response = await fetch(getOpenAIUrl(env), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${env.OPENAI_API_KEY}`, + }, + body: JSON.stringify({ + model: 'gpt-4o-mini', + messages, + tools: serverExpertTools, + tool_choice: 'auto', + max_tokens: 800, + temperature: 0.7, + }), + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error(`OpenAI API error: ${response.status} - ${error}`); + } + + const data = await response.json() as OpenAIAPIResponse; + const assistantMessage = data.choices[0].message; + + // Check if AI wants to call tools + if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) { + logger.info('도구 호출 요청', { + tools: assistantMessage.tool_calls.map(tc => tc.function.name), + }); + + // Add assistant message with tool calls + messages.push({ + role: 'assistant', + content: assistantMessage.content, + tool_calls: assistantMessage.tool_calls, + }); + + // Execute each tool and add results + for (const toolCall of assistantMessage.tool_calls) { + const args = JSON.parse(toolCall.function.arguments); + const result = await executeServerExpertTool(toolCall.function.name, args, env); + + messages.push({ + role: 'tool', + tool_call_id: toolCall.id, + name: toolCall.function.name, + content: result, + }); + + toolCallCount++; + } + + // Continue loop to get AI's response with tool results + continue; + } + + // No tool calls - parse the final response + const aiResponse = assistantMessage.content || ''; + logger.info('AI 응답', { response: aiResponse.slice(0, 200), toolCallCount }); + + // JSON 파싱 (마크다운 코드 블록 제거) + const jsonMatch = aiResponse.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/) || + aiResponse.match(/(\{[\s\S]*\})/); + + if (!jsonMatch) { + logger.error('JSON 파싱 실패', new Error('No JSON found'), { response: aiResponse }); + throw new Error('AI 응답 형식 오류'); + } + + const parsed = JSON.parse(jsonMatch[1]); + + // Validate response structure + if (!parsed.action || !parsed.message) { + throw new Error('Invalid AI response structure'); + } + + return { + action: parsed.action, + message: parsed.message, + collectedInfo: parsed.collectedInfo || session.collectedInfo, + }; + } + + // Max tool calls reached, force a recommendation + logger.warn('최대 도구 호출 횟수 도달', { toolCallCount }); + return { + action: 'recommend', + message: '분석이 완료되었습니다. 최적의 서버를 추천해 드리겠습니다.', + collectedInfo: session.collectedInfo, + }; + } catch (error) { + logger.error('Server Expert AI 호출 실패', error as Error); + throw error; + } +} + +// Main consultation processing +export async function processServerConsultation( + userMessage: string, + session: ServerSession, + env: Env +): Promise { + try { + logger.info('상담 처리 시작', { + userId: session.telegramUserId, + message: userMessage.slice(0, 50), + status: session.status + }); + + // Add user message to history + session.messages.push({ role: 'user', content: userMessage }); + + // Call Server Expert AI + const aiResult = await callServerExpertAI(env, session, userMessage); + + // Update collected info + session.collectedInfo = { ...session.collectedInfo, ...aiResult.collectedInfo }; + + // Add AI response to history + session.messages.push({ role: 'assistant', content: aiResult.message }); + + if (aiResult.action === 'recommend') { + // Mark session as recommending + session.status = 'recommending'; + await saveServerSession(env.SESSION_KV, session.telegramUserId, session); + + // Call recommendation API + logger.info('추천 API 호출', { collectedInfo: session.collectedInfo }); + + const { executeServerAction } = await import('./tools/server-tool'); + + const techStack = session.collectedInfo.useCase + ? inferTechStack(session.collectedInfo.useCase) + : ['web']; + + const expectedUsers = session.collectedInfo.scale + ? inferExpectedUsers(session.collectedInfo.scale) + : 100; + + const recommendation = await executeServerAction( + 'recommend', + { + tech_stack: techStack, + expected_users: expectedUsers, + use_case: session.collectedInfo.useCase || '웹 서비스', + region_preference: session.collectedInfo.regionPreference, + budget_limit: session.collectedInfo.budgetLimit, + lang: 'ko', + }, + env, + session.telegramUserId + ); + + // Mark session as completed and delete + session.status = 'completed'; + await deleteServerSession(env.SESSION_KV, session.telegramUserId); + + return `${aiResult.message}\n\n${recommendation}`; + } else { + // Continue gathering information + session.status = 'gathering'; + await saveServerSession(env.SESSION_KV, session.telegramUserId, session); + + return aiResult.message; + } + } catch (error) { + logger.error('상담 처리 실패', error as Error, { userId: session.telegramUserId }); + + // Clean up session on error + await deleteServerSession(env.SESSION_KV, session.telegramUserId); + + return '죄송합니다. 서버 추천 중 오류가 발생했습니다.\n다시 시도하려면 "서버 추천"이라고 말씀해주세요.'; + } +} diff --git a/src/summary-service.ts b/src/summary-service.ts index 2ee4c1f..904177d 100644 --- a/src/summary-service.ts +++ b/src/summary-service.ts @@ -383,7 +383,9 @@ ${integratedProfile} - 날씨, 시간, 계산 요청은 제공된 도구를 사용하세요. - 최신 정보, 실시간 데이터, 뉴스, 특정 사실 확인이 필요한 질문은 반드시 search_web 도구로 검색하세요. 자체 지식으로 답변하지 마세요. - 예치금, 입금, 충전, 잔액, 계좌 관련 요청은 반드시 manage_deposit 도구를 사용하세요. 금액 제한이나 규칙을 직접 판단하지 마세요. -- 서버, VPS, 클라우드, 호스팅, 인스턴스 관련 요청은 반드시 manage_server 도구를 사용하세요. 서버 추천 시 기술 스택과 예상 사용자 수를 확인하세요. 이전 대화에 서버 추천 결과가 있어도 항상 새로 도구를 호출하세요(가격/재고 변동). +- 서버, VPS, 클라우드, 호스팅 관련 요청: + • 첫 요청: manage_server(action="start_consultation")을 호출하여 상담 시작 + • 서버 상담 중인 메시지는 자동으로 전문가 AI에게 전달됨 (추가 처리 불필요) - 도메인 추천, 도메인 제안, 도메인 아이디어 요청은 반드시 suggest_domains 도구를 사용하세요. 직접 도메인을 나열하지 마세요. - 도메인/TLD 가격 조회(".com 가격", ".io 가격" 등)는 manage_domain 도구의 action=price를 사용하세요. - 기타 도메인 관련 요청(조회, 등록, 네임서버, WHOIS 등)은 manage_domain 도구를 사용하세요. diff --git a/src/tools/index.ts b/src/tools/index.ts index 2b5b9fd..4bf73e5 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -56,7 +56,8 @@ const SuggestDomainsArgsSchema = z.object({ }); const ManageServerArgsSchema = z.object({ - action: z.enum(['recommend', 'order', 'start', 'stop', 'delete', 'list']), + action: z.enum(['recommend', 'order', 'start', 'stop', 'delete', 'list', + 'start_consultation', 'continue_consultation', 'cancel_consultation']), tech_stack: z.array(z.string().min(1).max(100)).max(20).optional(), expected_users: z.number().int().positive().optional(), use_case: z.string().min(1).max(500).optional(), @@ -67,6 +68,7 @@ const ManageServerArgsSchema = z.object({ server_id: z.string().min(1).max(100).optional(), region_code: z.string().min(1).max(50).optional(), label: z.string().min(1).max(100).optional(), + message: z.string().min(1).max(500).optional(), // For continue_consultation }); // All tools array (used by OpenAI API) diff --git a/src/tools/server-tool.ts b/src/tools/server-tool.ts index 71ad239..ecfbc3d 100644 --- a/src/tools/server-tool.ts +++ b/src/tools/server-tool.ts @@ -136,32 +136,33 @@ export const manageServerTool = { type: 'function', function: { name: 'manage_server', - description: '클라우드 서버 관리 및 추천. "서버", "VPS", "클라우드", "호스팅" 등의 키워드가 포함되면 사용하세요.', + description: '클라우드 서버 관리 및 추천. 서버/VPS/클라우드/호스팅 관련 요청 시 사용. 상담 시작: start_consultation', parameters: { type: 'object', properties: { action: { type: 'string', - enum: ['recommend', 'order', 'start', 'stop', 'delete', 'list'], - description: 'recommend: 서버 추천, order: 서버 신청 (준비 중), start: 서버 켜기 (준비 중), stop: 서버 끄기 (준비 중), delete: 서버 해지 (준비 중), list: 내 서버 목록 (준비 중)', + enum: ['recommend', 'order', 'start', 'stop', 'delete', 'list', + 'start_consultation', 'continue_consultation', 'cancel_consultation'], + description: 'start_consultation: 서버 추천 상담 시작, continue_consultation: 상담 계속, recommend: 직접 추천', }, tech_stack: { type: 'array', items: { type: 'string' }, - description: '사용할 기술 스택 (예: ["nodejs", "nginx", "postgresql"]). recommend action에서 필수', + description: '기술 스택. 용도에서 추론 (블로그→wordpress, 쇼핑몰→ecommerce, 커뮤니티→php,mysql). 모르면 ["web"]', }, expected_users: { type: 'number', - description: '예상 동시 접속자 수. recommend action에서 필수', + description: '예상 사용자 수. 모르면 개인용=100, 사업용=500 사용', }, use_case: { type: 'string', - description: '사용 목적 설명 (예: "웹사이트 호스팅", "API 서버"). recommend action에서 필수', + description: '용도 (예: "블로그", "쇼핑몰", "커뮤니티")', }, traffic_pattern: { type: 'string', enum: ['steady', 'spiky', 'growing'], - description: '트래픽 패턴. steady: 일정한 트래픽, spiky: 순간 급증, growing: 점진적 성장. recommend action에서 선택', + description: '생략 가능. 기본값: steady', }, region_preference: { type: 'array', @@ -189,6 +190,10 @@ export const manageServerTool = { type: 'string', description: '서버 라벨 (예: "myapp-prod"). order action에서 필수', }, + message: { + type: 'string', + description: '사용자 메시지. continue_consultation action에서 필수', + }, }, required: ['action'], }, @@ -314,7 +319,7 @@ function formatRecommendations(data: RecommendResponse): string { } // 서버 작업 직접 실행 -async function executeServerAction( +export async function executeServerAction( action: string, args: { tech_stack?: string[]; @@ -327,6 +332,7 @@ async function executeServerAction( server_id?: string; region_code?: string; label?: string; + message?: string; }, env?: Env, telegramUserId?: string @@ -338,6 +344,76 @@ async function executeServerAction( }); switch (action) { + case 'start_consultation': { + // Import session functions + const { saveServerSession } = await import('../server-agent'); + + if (!telegramUserId) { + return '🚫 사용자 인증이 필요합니다.'; + } + + if (!env?.SESSION_KV) { + return '🚫 세션 저장소가 설정되지 않았습니다.'; + } + + const session: import('../types').ServerSession = { + telegramUserId, + status: 'gathering', + collectedInfo: {}, + messages: [], + createdAt: Date.now(), + updatedAt: Date.now(), + }; + + await saveServerSession(env.SESSION_KV, telegramUserId, session); + + logger.info('상담 세션 생성', { userId: maskUserId(telegramUserId) }); + + return '안녕하세요! 서버 추천을 도와드리겠습니다. 😊\n\n어떤 서비스를 운영하실 건가요?\n예: 블로그, 쇼핑몰, 커뮤니티, API 서버 등'; + } + + case 'continue_consultation': { + const { getServerSession, processServerConsultation } = await import('../server-agent'); + + if (!telegramUserId) { + return '🚫 사용자 인증이 필요합니다.'; + } + + if (!env?.SESSION_KV) { + return '🚫 세션 저장소가 설정되지 않았습니다.'; + } + + if (!args.message) { + return '🚫 메시지가 필요합니다.'; + } + + const session = await getServerSession(env.SESSION_KV, telegramUserId); + if (!session) { + return '세션이 만료되었습니다. 다시 시작하려면 "서버 추천"이라고 말씀해주세요.'; + } + + const result = await processServerConsultation(args.message, session, env); + return result; + } + + case 'cancel_consultation': { + const { deleteServerSession } = await import('../server-agent'); + + if (!telegramUserId) { + return '🚫 사용자 인증이 필요합니다.'; + } + + if (!env?.SESSION_KV) { + return '🚫 세션 저장소가 설정되지 않았습니다.'; + } + + await deleteServerSession(env.SESSION_KV, telegramUserId); + + logger.info('상담 세션 취소', { userId: maskUserId(telegramUserId) }); + + return '상담이 취소되었습니다. 다시 시작하려면 "서버 추천"이라고 말씀해주세요.'; + } + case 'recommend': { const { tech_stack, expected_users, use_case, traffic_pattern, region_preference, budget_limit, lang } = args; @@ -438,6 +514,7 @@ export async function executeManageServer( server_id?: string; region_code?: string; label?: string; + message?: string; }, env?: Env, telegramUserId?: string diff --git a/src/types.ts b/src/types.ts index df1cd4c..cba0bbf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -204,7 +204,10 @@ export interface ManageServerArgs { | "start" | "stop" | "delete" - | "list"; + | "list" + | "start_consultation" + | "continue_consultation" + | "cancel_consultation"; tech_stack?: string[]; expected_users?: number; use_case?: string; @@ -215,6 +218,22 @@ export interface ManageServerArgs { server_id?: string; region_code?: string; label?: string; + message?: string; // For continue_consultation +} + +// Server Consultation Session +export interface ServerSession { + telegramUserId: string; + status: 'gathering' | 'recommending' | 'completed'; + collectedInfo: { + useCase?: string; + scale?: 'personal' | 'business'; + budgetLimit?: number; + regionPreference?: string[]; + }; + messages: Array<{ role: 'user' | 'assistant'; content: string }>; + createdAt: number; + updatedAt: number; } // Deposit Agent 결과 타입