[STK8-04] 백엔드 ClaudeGatewayImpl Codex fallback 추가

작업 내용 (설계 의도)

변경 사항

ClaudeGatewayImpl은 현재 ProcessBuilder(["claude", "-p", ...]) 단일 호출만 한다. Claude CLI 실패(타임아웃·exit non-zero) 시 codex -q로 동일 프롬프트를 재시도하는 fallback 레이어를 추가한다.

설계 방향: 새 인프라 구현체 CodexGatewayImpl을 만들고, FallbackClaudeGateway 데코레이터가 Claude → Codex 순서로 위임한다. ChatConfig에서 FallbackClaudeGateway를 primary bean으로 등록한다.

기존 ClaudeGatewayImpl·ClaudeGateway 인터페이스는 수정하지 않는다.

ClaudeGateway (domain interface)
  ├── ClaudeGatewayImpl      ← 기존 (Claude CLI)
  ├── CodexGatewayImpl       ← 신규 (Codex CLI)
  └── FallbackClaudeGateway  ← 신규 데코레이터 (1차→2차 위임)

실패 감지 조건

  • IllegalStateException (타임아웃·exit non-zero) catch
  • 응답 문자열에 context length / token limit / rate limit / maximum context 포함

양쪽 다 실패 시

ChatGatewayUnavailableException을 던져 상위에서 500 응답 처리.

다이어그램

처리 흐름

sequenceDiagram
    participant DS as ChatDomainService
    participant FB as FallbackClaudeGateway
    participant CL as ClaudeGatewayImpl
    participant CO as CodexGatewayImpl

    DS->>FB: run(prompt)
    FB->>CL: run(prompt)
    alt Claude 성공
        CL-->>FB: response
        FB-->>DS: response
    else Claude 실패
        FB->>CO: run(prompt)
        alt Codex 성공
            CO-->>FB: response
            FB-->>DS: response
        else Codex 실패
            FB-->>DS: ChatGatewayUnavailableException
        end
    end

클래스 의존

flowchart LR
    DS[ChatDomainService] --> GW[ClaudeGateway]
    FB[FallbackClaudeGateway] -.->|implements| GW
    FB --> CL[ClaudeGatewayImpl]
    FB --> CO[CodexGatewayImpl]
    CL -.->|implements| GW
    CO -.->|implements| GW

테스트 케이스

  • Claude 성공 → Codex 미호출, Claude 응답 반환
  • Claude 타임아웃 → Codex fallback 호출됨
  • Claude exit non-zero → Codex fallback 호출됨
  • Claude 토큰 한도 감지 → Codex fallback 호출됨
  • Codex 성공 → Codex 응답 반환
  • Claude·Codex 둘 다 실패 → ChatGatewayUnavailableException 발생