# AI Gateway — Relay Bridge

## Project Overview

Express.js API gateway: proxy/translator giữa Anthropic Messages API ↔ OpenAI Chat/Responses API. Forward request lên OpenModel.ai backend. Chạy trên cPanel Node.js hosting.

## Kiến trúc

```
Client → auth → adapter/toAnthropic.js → cache → services/openModelClient.js → OpenModel.ai
                                         → adapter/fromAnthropic.js → Response
```

**Luồng request:**
1. Client gửi request theo format Anthropic hoặc OpenAI
2. `middleware/auth.js` — kiểm tra API key
3. `adapter/toAnthropic.js` — chuyển đổi format OpenAI → Anthropic (nếu cần)
4. `services/cache.js` — kiểm tra cache, nếu miss thì gọi upstream
5. `services/openModelClient.js` — gửi request lên OpenModel.ai
6. `adapter/fromAnthropic.js` — chuyển đổi response Anthropic → OpenAI (nếu cần)
7. Trả response về client

## Coding Standards

### Tư duy & Giải quyết vấn đề — Giao thức 5 bước

Trước khi viết 1 dòng code, bắt buộc chạy qua 5 bước:

**Bước 1 — Xác định vấn đề:** 
Nói lại requirement bằng tiếng Việt, ngắn gọn. Nếu thấy mơ hồ → hỏi lại, không tự suy diễn.

**Bước 2 — Phân tích impact:**
- Code này ảnh hưởng file nào? Có an toàn với logic đang chạy không?
- Nếu thêm flag mới → mặc định OFF, behavior cũ không đổi
- Cần test thủ công cái gì sau khi code?

**Bước 3 — Edge cases & Lỗi có thể xảy ra:**
- Input null/undefined/empty
- Network timeout upstream
- Concurrent requests (race condition)
- Memory leak (cache không xoá, event emitter không detach)
- Nếu có throw → có catch không? Format error có đúng không?

**Bước 4 — Thiết kế giải pháp:**
- Tối giản nhất có thể: 1 file mới + 2-3 dòng tích hợp, không refactor cả project
- Dùng module đã có sẵn: kiểm tra services/, utils/, middleware/ trước khi viết mới
- Nếu giải pháp cần >50 dòng → cân nhắc lại

**Bước 5 — Kiểm tra kết quả:**
- Mô tả cách verify: curl gì? response kỳ vọng gì?
- Có ảnh hưởng ngược (regression) không?

Ví dụ: khi thêm 1 endpoint mới, chuỗi suy nghĩ phải là:
> "Cần thêm GET /v1/foo. File nào bị ảnh hưởng? Chỉ routes/health.js thôi. Đã có express router sẵn. Thêm 1 handler, try/catch với format error. Cache được không? Không vì là realtime. Xong → test bằng curl localhost:3000/v1/foo."

### Frontend (HTML/CSS/JS) — Luật BẮT BUỘC (đọc kỹ trước khi code)

#### FONT: CHỈ 1 DÒNG DUY NHẤT
```css
font-family: Arial, Helvetica, sans-serif;  /* UI */
font-family: Consolas, 'Courier New', monospace;  /* code */
```
- **KHÔNG ĐƯỢC** dùng: Inter, SF Pro, JetBrains Mono, Roboto, Poppins, Nunito, system-ui, -apple-system, Google Fonts bất kỳ, @import, @font-face
- **Lý do:** Arial là font chắc nhất, không AI, có sẵn trên mọi máy. Consolas là font code sắc nét nhất Windows.

#### ICON: 0 icon nếu không có lý do chính đáng
- **KHÔNG ĐƯỢC** thêm icon trang trí — không emoji, không SVG, không unicode decorative
- Icon CHỈ được dùng nếu có chức năng rõ ràng (copy, close, toggle). Mỗi icon phải tự hỏi: "thiếu nó có sao không?" Nếu "không" → xoá.

#### MÀU SẮC: tối giản, tuỳ theo thương hiệu

- **3-4 màu tối đa** cho toàn bộ trang: nền, surface, text (chính + phụ), 1 accent, 1 border
- **Tuyệt đối không** gradient, blur, glow, shadow to, animation, pattern nền, backdrop-filter
- **KHÔNG ĐƯỢC** dùng màu neon, màu quá sáng, rainbow palette
- border-radius: 4px mặc định, 8px tối đa
- Accent: chọn 1 màu duy nhất — xanh, đen, hoặc màu brand. Không mix cả 2.
- Luôn có `--border` riêng, không dùng shadow làm viền
- Màu được chọn phải có tương phản tốt (kiểm tra WCAG nếu cần)

#### KIỂM TRA NHANH (tự kiểm trước khi báo done)
- [ ] Có Google Fonts/@font-face không? → XOÁ NGAY
- [ ] Có Inter/SF/JetBrains không? → ĐỔI THÀNH Arial/Consolas
- [ ] Có icon/emoji vô nghĩa không? → XOÁ HẾT
- [ ] Có gradient/blur/glow/animation không? → XOÁ HẾT
- [ ] Có border-radius > 8px không? → GIẢM XUỐNG

### Backend (Node.js/Express)

**Cấu trúc code:**
- Route handler → gọi service → trả response. Không business logic trong route
- Config qua env var — 0 hardcode. Mọi URL, key, threshold phải configurable
- Error handling: `try/catch` mọi async route, format response theo chuẩn Anthropic hoặc OpenAI

**Performance:**
- Async I/O wherever possible — sync chỉ dùng ở startup
- Cache response identical request (LRU + TTL)
- Log fire-and-forget, không await

**Ghi nhớ:**
- Project `CACHE_ENABLED=true` mặc định
- Mọi tính năng mới thêm vào phải có flag tắt được, không ảnh hưởng behavior cũ
- Không xoá/sửa code đang chạy — viết mới + tích hợp

## File Map

```
app.js                        # Entry point, middleware chain, graceful shutdown
services/
  cache.js                    # LRU in-memory cache (key: MD5 body)
  openModelClient.js          # HTTP client gọi upstream
  tokenTracker.js             # Đếm token in/out
routes/
  health.js                   # GET /health, /v1/ping, /v1/stats
  anthropic.js                # Anthropic Messages API routes
  openai.js                   # OpenAI Chat/Responses API routes
adapters/
  toAnthropic.js              # OpenAI → Anthropic format
  fromAnthropic.js            # Anthropic → OpenAI format
middleware/
  auth.js                     # Xác thực bằng x-api-key / Bearer
  rateLimit.js                # Rate limiter
  logging.js                  # Async access log
public/
  index.html                  # Dashboard
```

## Environment (hiện tại)

```
CACHE_ENABLED=true            # Bật cache response
CACHE_TTL_MS=3600000          # 60 phút — cache cho cả buổi code
CACHE_MAX_ITEMS=50000         # ~500MB RAM, host 4GB
REQUEST_TIMEOUT_MS=300000     # 5 phút cho gen dài
TOKEN_COST_CACHE_HIT_INPUT_VND=71    # Cache hit: ~71₫/1M
TOKEN_COST_CACHE_MISS_INPUT_VND=3560 # Cache miss: ~3.560₫/1M
TOKEN_COST_OUTPUT_VND_PER_1M=7120    # Output: ~7.120₫/1M
```

## Token tracking

- `/v1/stats` — tổng hợp global + per-key + cache hit/miss + quy đổi VND
- `cost_vnd.actual` = chi phí thực tế (cache hit tính giá rẻ, cache miss tính giá đầy đủ)
- `cost_vnd.no_cache` = chi phí nếu không có cache
- `cost_vnd.saved` = số tiền tiết kiệm nhờ cache
