[STK6-05] Chat REST API 엔드포인트 (claude -p 애그리게이터)

작업 내용 (설계 의도)

변경 사항

presentation/ai/ChatApiController.kt를 신규 생성한다. POST /api/chat/message 엔드포인트를 제공하며 ProcessBuilderclaude -p subprocess를 실행한다. --mcp-configstock-mcp.json 경로를 전달하고, --output-format stream-json으로 스트리밍 JSON을 파싱해 SSE로 클라이언트에 전달한다. 대화 히스토리는 ConcurrentHashMap<sessionId, List<String>> 인메모리로 유지하고 프롬프트에 prepend한다.

선행 티켓: STK6-01, STK6-02, STK6-03

핵심 구현 포인트

  • subprocess 스트림은 별도 Thread에서 blocking read → SSE emitter send
  • SseEmitter timeout: 120초 (claude -p 최대 응답 시간 고려)
  • claude -p 비정상 종료(exit code ≠ 0) 시 SSE error 이벤트 전송
  • --allowedTools "mcp__stock__*,mcp__brave-search__*" 로 허용 Tool 제한

다이어그램

처리 흐름

sequenceDiagram
    participant FE as Chat UI
    participant API as ChatApiController
    participant PB as ProcessBuilder
    participant CLI as claude -p

    FE->>API: POST /api/chat/message {sessionId, message}
    API->>API: history 조회 + 프롬프트 조립
    API->>PB: ProcessBuilder(claude -p, --mcp-config, --output-format stream-json)
    PB->>CLI: subprocess 실행
    CLI-->>PB: stream-json 청크 (라인별)
    PB-->>API: InputStream 스트리밍
    API-->>FE: SSE send(chunk)
    CLI-->>PB: EOF (종료)
    PB-->>API: process.waitFor()
    API-->>FE: SSE complete

클래스 의존

flowchart LR
    ChatApiController -->|subprocess| ClaudeCLI[claude -p]
    ChatApiController -->|read/write| SessionMap[ConcurrentHashMap]
    ChatApiController -->|config inject| McpConfigPath[MCP_CONFIG_PATH]

테스트 케이스

  • POST /api/chat/message 요청 시 ProcessBuilderclaude -p 인자로 실행된다.
  • --mcp-config 인자가 MCP_CONFIG_PATH 환경 변수 값으로 설정된다.
  • 동일 sessionId로 2회 요청 시 첫 번째 대화가 두 번째 프롬프트에 포함된다.
  • sessionId가 빈 문자열이면 400 Bad Request를 반환한다.
  • subprocess exit code가 1이면 SSE error 이벤트를 전송하고 emitter를 종료한다.
  • subprocess가 120초 내에 응답하지 않으면 SSE timeout 이벤트를 전송한다.