앱이 기대하는 base-backend 계약(envelope·JWT·/chat/stream SSE)을 그대로 구현. Django 는 로그인·세션 미러(SQLite/PG)·OpenCode 이벤트 번역만 맡고, 답변은 전용 OpenCode 인스턴스(opencode/ workspace, codeassist 에이전트)가 만듦. - accounts: 이메일 로그인, access 60분 / refresh 14일, entra/config 는 501 - chat: 세션 목록·검색·메시지·취소 + POST /chat/stream 어댑터(part.delta→token, idle→usage/done, 클라 끊겨도 턴 감시 태스크가 DB 마무리, 첫 이벤트 60초 타임아웃) - OpenCode 1.18 멀티 프로젝트라 모든 요청에 ?directory= 부착 - docs-lib 에 OpenCode SDK 1.18.6 타입 원본, pytest 32 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
5_django_backend — CodeAssist 백엔드 (Django + OpenCode)
프론트(2_frontend)가 기대하는 base-backend 계약을 그대로 구현한 Django 서버. AI 답변은 CodeAssist 전용 OpenCode 서버(opencode/)가 만들고, 여기는 인증·세션 미러·SSE 어댑터를 맡음.
ABAP_OPENCODE web/BE 에서 뼈대(settings·JWT·OpenCode 클라)를 복사해 왔고, 그쪽 서버와는 독립.
CodeAssist(Tauri/React) ──/api/v1──▶ Django(uvicorn :8001) ──HTTP/SSE──▶ opencode serve(:4096) ──▶ LLM
Bearer JWT (localStorage) ├─ accounts 로그인·refresh
├─ chat 세션·검색 미러(SQLite/PG) + /chat/stream 어댑터
└─ opencode/ workspace (AGENTS.md·agent·opencode.json)
띄우기
# 1) OpenCode (터미널 1)
cd 5_django_backend/opencode
export OPENROUTER_API_KEY=sk-or-...
npx -y opencode-ai@1.18.6 serve --hostname 127.0.0.1 --port 4096
# 2) Django (터미널 2)
cd 5_django_backend
python3.12 -m venv .venv && .venv/bin/pip install -r requirements.txt
cp .env.example .env # 필요하면 값 수정
.venv/bin/python manage.py migrate # 빈 DB 면 admin@codeassist.local / guest@codeassist.local (test1234) 시드
.venv/bin/uvicorn config.asgi:application --host 0.0.0.0 --port 8001 --reload
# 3) 프론트 (터미널 3) — .env 의 VITE_DEV_API_TARGET 기본이 :8001 이라 그대로
cd 2_frontend && npm run dev
runserver 말고 uvicorn(ASGI) 이어야 SSE 가 버퍼링 없이 흐름.
API (전부 envelope CommonResponse)
| 엔드포인트 | 설명 |
|---|---|
GET /api/v1/health |
상태 (무인증) |
POST /api/v1/auth/login {email,password} |
TokenResponse — access 60분 / refresh 14일 |
POST /api/v1/auth/refresh {refreshToken} |
access 재발급 (refresh 회전 안 함). 없으면 401 REFRESH_TOKEN_MISSING |
POST /api/v1/auth/logout |
null (클라가 토큰 버림) |
GET /api/v1/users/me |
UserResponse |
GET /api/v1/auth/entra/config |
항상 501 — 프론트가 Entra 버튼 숨김 |
GET /api/v1/chat/sessions?page&limit |
내 세션 목록, 최근순 + meta |
POST /api/v1/chat/sessions |
OpenCode 세션 생성 + 미러 |
GET /api/v1/chat/sessions/search?query&page&limit |
내 메시지 본문 검색 |
GET /api/v1/chat/sessions/{id}/messages |
세션 + 메시지 (isGenerating 폴링용) |
POST /api/v1/chat/sessions/{id}/cancel |
OpenCode abort + isGenerating 해제 |
POST /api/v1/chat/stream |
SSE — token{delta}… title{title} usage{used,limit,ratio,elapsed_ms} done{} / error{message,code} |
에러 code: INVALID_CREDENTIALS REFRESH_TOKEN_MISSING REFRESH_TOKEN_INVALID CHAT_GENERATION_IN_PROGRESS(409) UPSTREAM_UNAVAILABLE(503) LLM_ERROR LLM_ABORTED.
스트림이 도는 법 (apps/chat/stream.py)
- Bearer 검사 → 내 세션 →
is_generating이면 409. - user 메시지 미러 저장,
is_generating=true. apps/chat/events.py버스(프로세스당 하나가 OpenCode/event구독) 에 세션 큐 등록 →POST /session/{id}/prompt_async.message.part.updated(text, delta) →token,session.updated제목 →title,session.idle→ OpenCode 에서 최종 메시지·tokens·cost 가져와 assistant 미러 저장 →usage→done.session.error→error+ 부분 답변 저장.- 이 감시는 클라이언트와 별개 태스크라 창을 닫아도 idle 까지 돌고 DB 를 마무리함 → 프론트
isGenerating폴링이 답변 복구.
테스트
.venv/bin/python -m pytest -q # OpenCode 는 가짜(tests/test_stream.py FakeOpencode)
폴더
config/ settings · urls · asgi
common/ envelope.py(응답 포장·CodedError) · opencode_service.py(OpenCode HTTP/SSE)
apps/accounts User(email 로그인) · JWT · 시드
apps/chat models(ChatSession/ChatMessage) · views · events(버스) · stream(어댑터)
opencode/ OpenCode workspace — AGENTS.md · opencode.json · .opencode/agent/codeassist.md
docs-lib/ OpenCode SDK 1.18.6 타입 (API 진실원천)
tests/ pytest