# 移除 LiteLLM，統一用量追蹤實作指南

## 概述

將現有架構從 LiteLLM Proxy 改為統一的用量追蹤系統，**同時支援對話 API 和 Claude Agent SDK 檔案操作**。

### 你的系統有兩種 AI 用途

| 用途 | 程式碼 | 功能 | 追蹤方式 |
|------|--------|------|---------|
| **對話 API** | `kenttest.py` | 純文字問答 | Server Proxy |
| **檔案操作** | `ai_brain.py` | 寫程式、改檔案 | SDK ResultMessage |

### 架構變更

```
【現有架構】
kenttest.py ──► LiteLLM (4000) ──► Anthropic  ❌ 要拔掉
ai_brain.py ──► Claude Agent SDK ──► Anthropic  （無追蹤）

【新架構】
                    ┌─────────────────────────────┐
                    │     你的 Flask Server       │
                    │     (統一用量追蹤中心)       │
                    │                             │
                    │  POST /api/chat   ◄─────────┼─── kenttest.py（對話）
                    │  POST /api/report-usage ◄───┼─── ai_brain.py（檔案操作）
                    │  GET  /api/usage  ──────────┼──► 用戶查詢用量
                    │                             │
                    └─────────────────────────────┘
                              │
                              ▼
                    ┌─────────────────────────────┐
                    │         Database            │
                    │    (users + usage_log)      │
                    └─────────────────────────────┘
```

**改動範圍：**
- Flask Server：新增 3 個 API 端點 + 資料庫
- Client（kenttest.py）：改 API endpoint URL
- ai_brain.py：新增用量回報邏輯（約 20 行）

---

## 第一部分：Flask Server 端實作

### 1. 安裝依賴

```bash
pip install anthropic flask flask-cors
```

### 2. 資料庫 Schema（SQLite）

```sql
-- schema.sql

-- 用戶表（Google SSO 登入後建立）
CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    email TEXT UNIQUE NOT NULL,
    google_id TEXT,
    user_token TEXT UNIQUE NOT NULL,    -- 用於 API 認證的 token
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    is_active BOOLEAN DEFAULT 1
);

-- 用量記錄表
CREATE TABLE IF NOT EXISTS usage_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL,
    model TEXT NOT NULL,
    input_tokens INTEGER NOT NULL,
    output_tokens INTEGER NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

-- 索引加速查詢
CREATE INDEX IF NOT EXISTS idx_usage_user_id ON usage_log(user_id);
CREATE INDEX IF NOT EXISTS idx_usage_created_at ON usage_log(created_at);
```

### 3. Flask Server 完整程式碼

```python
# app.py - Flask Server with Anthropic Proxy

import os
import secrets
import sqlite3
from datetime import datetime, timedelta
from functools import wraps

from flask import Flask, request, jsonify, g
from flask_cors import CORS
from anthropic import Anthropic

app = Flask(__name__)
CORS(app)

# ============================================================
# 配置
# ============================================================

ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "sk-ant-xxx")
DATABASE_PATH = os.environ.get("DATABASE_PATH", "claude_proxy.db")

# 模型映射（簡化名稱 -> 官方模型 ID）
MODEL_MAP = {
    "claude-haiku": "claude-haiku-4-20250514",
    "claude-sonnet": "claude-sonnet-4-20250514",
    "claude-opus": "claude-opus-4-20250514",
    # 也支援直接使用官方 ID
}

# Anthropic Client（全域單例）
anthropic_client = Anthropic(api_key=ANTHROPIC_API_KEY)

# ============================================================
# 資料庫操作
# ============================================================

def get_db():
    """取得資料庫連線"""
    if 'db' not in g:
        g.db = sqlite3.connect(DATABASE_PATH)
        g.db.row_factory = sqlite3.Row
    return g.db

@app.teardown_appcontext
def close_db(error):
    """關閉資料庫連線"""
    db = g.pop('db', None)
    if db is not None:
        db.close()

def init_db():
    """初始化資料庫"""
    conn = sqlite3.connect(DATABASE_PATH)
    conn.executescript('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            email TEXT UNIQUE NOT NULL,
            google_id TEXT,
            user_token TEXT UNIQUE NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            is_active BOOLEAN DEFAULT 1
        );

        CREATE TABLE IF NOT EXISTS usage_log (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            model TEXT NOT NULL,
            input_tokens INTEGER NOT NULL,
            output_tokens INTEGER NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users(id)
        );

        CREATE INDEX IF NOT EXISTS idx_usage_user_id ON usage_log(user_id);
        CREATE INDEX IF NOT EXISTS idx_usage_created_at ON usage_log(created_at);
    ''')
    conn.commit()
    conn.close()
    print("✅ 資料庫初始化完成")

# ============================================================
# 認證相關
# ============================================================

def require_auth(f):
    """認證裝飾器"""
    @wraps(f)
    def decorated(*args, **kwargs):
        auth_header = request.headers.get('Authorization', '')

        if not auth_header.startswith('Bearer '):
            return jsonify({"error": "Missing or invalid Authorization header"}), 401

        token = auth_header[7:]  # 移除 "Bearer " 前綴

        db = get_db()
        user = db.execute(
            'SELECT * FROM users WHERE user_token = ? AND is_active = 1',
            (token,)
        ).fetchone()

        if not user:
            return jsonify({"error": "Invalid or expired token"}), 401

        g.current_user = dict(user)
        return f(*args, **kwargs)

    return decorated

def generate_user_token():
    """產生安全的 user token"""
    return f"ut-{secrets.token_urlsafe(32)}"

# ============================================================
# Google SSO 回調（修改你現有的登入邏輯）
# ============================================================

@app.route('/auth/google-callback', methods=['POST'])
def google_callback():
    """
    Google SSO 成功後呼叫此端點
    建立或更新用戶，回傳 user_token
    """
    data = request.json
    email = data.get('email')
    google_id = data.get('google_id')

    if not email:
        return jsonify({"error": "Email is required"}), 400

    db = get_db()

    # 檢查用戶是否存在
    existing_user = db.execute(
        'SELECT * FROM users WHERE email = ?', (email,)
    ).fetchone()

    if existing_user:
        # 用戶已存在，返回現有 token
        user_token = existing_user['user_token']
    else:
        # 新用戶，建立記錄
        user_token = generate_user_token()
        db.execute(
            'INSERT INTO users (email, google_id, user_token) VALUES (?, ?, ?)',
            (email, google_id, user_token)
        )
        db.commit()

    return jsonify({
        "user_token": user_token,
        "email": email
    })

@app.route('/auth/auto-login', methods=['GET'])
def auto_login():
    """
    原有的自動登入端點
    保持與 kenttest.py 相容
    """
    callback = request.args.get('callback')

    # ... 你現有的 Google SSO 邏輯 ...
    # 登入成功後，呼叫 google_callback 取得 user_token
    # 然後 redirect 到 callback URL，帶上 user_token 和 email

    # 範例：redirect 到 callback
    # return redirect(f"{callback}?user_token={token}&email={email}")

    pass  # 保留你現有的實作

# ============================================================
# Claude API Proxy（核心功能）
# ============================================================

@app.route('/api/chat', methods=['POST'])
@require_auth
def chat():
    """
    代理 Claude API 請求
    記錄用量到資料庫
    """
    data = request.json
    user = g.current_user

    # 驗證必要參數
    if 'messages' not in data:
        return jsonify({"error": "messages is required"}), 400

    # 解析模型（支援簡化名稱）
    model_input = data.get('model', 'claude-sonnet')
    model = MODEL_MAP.get(model_input, model_input)

    max_tokens = data.get('max_tokens', 1024)
    messages = data['messages']
    system = data.get('system')  # 可選的 system prompt

    try:
        # 呼叫 Anthropic API
        kwargs = {
            "model": model,
            "max_tokens": max_tokens,
            "messages": messages
        }
        if system:
            kwargs["system"] = system

        response = anthropic_client.messages.create(**kwargs)

        # 記錄用量
        db = get_db()
        db.execute('''
            INSERT INTO usage_log (user_id, model, input_tokens, output_tokens)
            VALUES (?, ?, ?, ?)
        ''', (
            user['id'],
            model,
            response.usage.input_tokens,
            response.usage.output_tokens
        ))
        db.commit()

        # 回傳結果（保持與原 LiteLLM 相容的格式）
        return jsonify({
            "content": [{"text": response.content[0].text}],
            "model": response.model,
            "usage": {
                "input_tokens": response.usage.input_tokens,
                "output_tokens": response.usage.output_tokens
            }
        })

    except Exception as e:
        return jsonify({"error": str(e)}), 500

@app.route('/v1/messages', methods=['POST'])
@require_auth
def messages_compat():
    """
    相容 Anthropic /v1/messages 格式
    讓現有 client 不用改太多
    """
    return chat()

# ============================================================
# 用量查詢 API
# ============================================================

@app.route('/api/usage', methods=['GET'])
@require_auth
def get_usage():
    """查詢當前用戶的用量統計"""
    user = g.current_user
    db = get_db()

    # 總用量
    total = db.execute('''
        SELECT
            COALESCE(SUM(input_tokens), 0) as total_input,
            COALESCE(SUM(output_tokens), 0) as total_output,
            COUNT(*) as total_requests
        FROM usage_log
        WHERE user_id = ?
    ''', (user['id'],)).fetchone()

    # 本月用量
    first_day_of_month = datetime.now().replace(day=1, hour=0, minute=0, second=0)
    monthly = db.execute('''
        SELECT
            COALESCE(SUM(input_tokens), 0) as monthly_input,
            COALESCE(SUM(output_tokens), 0) as monthly_output,
            COUNT(*) as monthly_requests
        FROM usage_log
        WHERE user_id = ? AND created_at >= ?
    ''', (user['id'], first_day_of_month)).fetchone()

    # 今日用量
    today = datetime.now().replace(hour=0, minute=0, second=0)
    daily = db.execute('''
        SELECT
            COALESCE(SUM(input_tokens), 0) as daily_input,
            COALESCE(SUM(output_tokens), 0) as daily_output,
            COUNT(*) as daily_requests
        FROM usage_log
        WHERE user_id = ? AND created_at >= ?
    ''', (user['id'], today)).fetchone()

    return jsonify({
        "user": user['email'],
        "total": {
            "input_tokens": total['total_input'],
            "output_tokens": total['total_output'],
            "requests": total['total_requests'],
            "total_tokens": total['total_input'] + total['total_output']
        },
        "monthly": {
            "input_tokens": monthly['monthly_input'],
            "output_tokens": monthly['monthly_output'],
            "requests": monthly['monthly_requests'],
            "total_tokens": monthly['monthly_input'] + monthly['monthly_output']
        },
        "daily": {
            "input_tokens": daily['daily_input'],
            "output_tokens": daily['daily_output'],
            "requests": daily['daily_requests'],
            "total_tokens": daily['daily_input'] + daily['daily_output']
        }
    })

@app.route('/api/usage/history', methods=['GET'])
@require_auth
def get_usage_history():
    """查詢用量歷史記錄"""
    user = g.current_user
    db = get_db()

    # 可選參數
    days = request.args.get('days', 30, type=int)
    limit = request.args.get('limit', 100, type=int)

    since = datetime.now() - timedelta(days=days)

    records = db.execute('''
        SELECT model, input_tokens, output_tokens, created_at
        FROM usage_log
        WHERE user_id = ? AND created_at >= ?
        ORDER BY created_at DESC
        LIMIT ?
    ''', (user['id'], since, limit)).fetchall()

    return jsonify({
        "user": user['email'],
        "history": [
            {
                "model": r['model'],
                "input_tokens": r['input_tokens'],
                "output_tokens": r['output_tokens'],
                "total_tokens": r['input_tokens'] + r['output_tokens'],
                "created_at": r['created_at']
            }
            for r in records
        ]
    })

@app.route('/api/usage/daily-summary', methods=['GET'])
@require_auth
def get_daily_summary():
    """查詢每日用量統計"""
    user = g.current_user
    db = get_db()

    days = request.args.get('days', 30, type=int)
    since = datetime.now() - timedelta(days=days)

    summary = db.execute('''
        SELECT
            DATE(created_at) as date,
            SUM(input_tokens) as input_tokens,
            SUM(output_tokens) as output_tokens,
            COUNT(*) as requests
        FROM usage_log
        WHERE user_id = ? AND created_at >= ?
        GROUP BY DATE(created_at)
        ORDER BY date DESC
    ''', (user['id'], since)).fetchall()

    return jsonify({
        "user": user['email'],
        "daily_summary": [
            {
                "date": r['date'],
                "input_tokens": r['input_tokens'],
                "output_tokens": r['output_tokens'],
                "total_tokens": r['input_tokens'] + r['output_tokens'],
                "requests": r['requests']
            }
            for r in summary
        ]
    })

# ============================================================
# 健康檢查
# ============================================================

@app.route('/health', methods=['GET'])
def health():
    """健康檢查端點"""
    return jsonify({"status": "ok"})

# ============================================================
# 啟動
# ============================================================

if __name__ == '__main__':
    init_db()
    app.run(host='0.0.0.0', port=5000, debug=True)
```

---

## 第二部分：Client 端修改

### 修改 kenttest.py

只需要改幾行：

```python
# kenttest.py 修改版

class ClaudeClient:
    def __init__(self, server_url="http://35.229.221.64.nip.io:5000"):
        self.server_url = server_url
        # 移除 litellm_url，現在只需要一個 server
        self.config_file = Path.home() / '.claude_client_config.json'
        self.user_token = None  # 改名：api_key -> user_token
        self.user_email = None
        self.callback_port = 8888

        self._load_config()

        if not self.user_token:
            self._auto_login()

    def _load_config(self):
        """從本地載入配置"""
        if self.config_file.exists():
            try:
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    config = json.load(f)
                    self.user_token = config.get('user_token')  # 改名
                    self.user_email = config.get('user_email')

                if self.user_token:
                    print(f"✅ 已載入本地配置")
                    print(f"   用戶: {self.user_email}")

                    if not self._verify_token():
                        print("⚠️  Token 已失效，需要重新登入")
                        self.user_token = None
            except Exception as e:
                print(f"⚠️  載入配置失敗: {e}")

    def _verify_token(self):
        """驗證 token 是否有效"""
        try:
            response = requests.get(
                f"{self.server_url}/health",
                timeout=5
            )
            return response.status_code == 200
        except:
            return False

    def query(self, prompt, model="claude-sonnet", max_tokens=1024):
        """查詢 Claude"""
        if not self.user_token:
            print("❌ 未登入")
            return None

        try:
            response = requests.post(
                f"{self.server_url}/api/chat",  # 改用新端點
                headers={
                    'Authorization': f'Bearer {self.user_token}',  # 用 user_token
                    'Content-Type': 'application/json'
                },
                json={
                    'model': model,
                    'max_tokens': max_tokens,
                    'messages': [
                        {'role': 'user', 'content': prompt}
                    ]
                },
                timeout=60
            )

            if response.status_code == 200:
                result = response.json()
                return result['content'][0]['text']
            else:
                print(f"❌ 請求失敗: {response.status_code}")
                return None

        except Exception as e:
            print(f"❌ 錯誤: {e}")
            return None

    def get_usage(self):
        """查詢用量統計"""
        if not self.user_token:
            print("❌ 未登入")
            return None

        try:
            response = requests.get(
                f"{self.server_url}/api/usage",
                headers={'Authorization': f'Bearer {self.user_token}'},
                timeout=10
            )

            if response.status_code == 200:
                return response.json()
            else:
                print(f"❌ 查詢失敗: {response.status_code}")
                return None
        except Exception as e:
            print(f"❌ 錯誤: {e}")
            return None
```

### 新增用量查詢功能

```python
# 使用範例
client = ClaudeClient()

# 查詢 Claude
response = client.query("Hello!")
print(response)

# 查詢我的用量
usage = client.get_usage()
print(f"""
📊 用量統計
====================
總計: {usage['total']['total_tokens']} tokens
本月: {usage['monthly']['total_tokens']} tokens
今日: {usage['daily']['total_tokens']} tokens
""")
```

---

## 第三部分：部署步驟

### 1. 伺服器端

```bash
# 1. 設定環境變數
export ANTHROPIC_API_KEY="sk-ant-api03-xxx"
export DATABASE_PATH="/var/data/claude_proxy.db"

# 2. 安裝依賴
pip install anthropic flask flask-cors

# 3. 初始化資料庫
python -c "from app import init_db; init_db()"

# 4. 啟動服務（生產環境用 gunicorn）
gunicorn -w 4 -b 0.0.0.0:5000 app:app
```

### 2. 整合到現有 Flask App

如果你已經有 Flask app，只需要：

```python
# 在你現有的 app.py 中加入

from anthropic import Anthropic

anthropic_client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

# 然後把上面的路由加進去
# @app.route('/api/chat', ...)
# @app.route('/api/usage', ...)
```

---

## 第四部分：API 文件

### POST /api/chat

代理 Claude API 請求

**Headers:**
```
Authorization: Bearer {user_token}
Content-Type: application/json
```

**Request Body:**
```json
{
    "model": "claude-sonnet",
    "max_tokens": 1024,
    "messages": [
        {"role": "user", "content": "Hello!"}
    ],
    "system": "You are a helpful assistant."  // 可選
}
```

**Response:**
```json
{
    "content": [{"text": "Hello! How can I help you?"}],
    "model": "claude-sonnet-4-20250514",
    "usage": {
        "input_tokens": 10,
        "output_tokens": 15
    }
}
```

---

### GET /api/usage

查詢用戶用量統計

**Headers:**
```
Authorization: Bearer {user_token}
```

**Response:**
```json
{
    "user": "user@example.com",
    "total": {
        "input_tokens": 10000,
        "output_tokens": 20000,
        "total_tokens": 30000,
        "requests": 150
    },
    "monthly": {
        "input_tokens": 5000,
        "output_tokens": 10000,
        "total_tokens": 15000,
        "requests": 80
    },
    "daily": {
        "input_tokens": 500,
        "output_tokens": 1000,
        "total_tokens": 1500,
        "requests": 10
    }
}
```

---

### GET /api/usage/history

查詢用量歷史記錄

**Query Parameters:**
- `days`: 查詢天數（預設 30）
- `limit`: 記錄數量上限（預設 100）

**Response:**
```json
{
    "user": "user@example.com",
    "history": [
        {
            "model": "claude-sonnet-4-20250514",
            "input_tokens": 100,
            "output_tokens": 200,
            "total_tokens": 300,
            "created_at": "2025-11-30 10:30:00"
        }
    ]
}
```

---

### GET /api/usage/daily-summary

查詢每日用量統計

**Response:**
```json
{
    "user": "user@example.com",
    "daily_summary": [
        {
            "date": "2025-11-30",
            "input_tokens": 5000,
            "output_tokens": 10000,
            "total_tokens": 15000,
            "requests": 50
        }
    ]
}
```

---

---

## 第五部分：ai_brain.py 用量回報（關鍵！）

這是讓 **Claude Agent SDK 檔案操作也能追蹤用量** 的關鍵。

### Claude Agent SDK 的 ResultMessage

SDK 執行完成後會回傳 `ResultMessage`，包含：

```python
ResultMessage:
    usage: dict[str, Any] | None     # {"input_tokens": 1000, "output_tokens": 500}
    total_cost_usd: float | None     # 0.015
    duration_ms: int                  # 執行時間
    num_turns: int                    # 對話輪數
```

### 修改 ai_brain.py

```python
# src/fwauto/nodes/ai_brain.py

import os
import requests
from claude_agent_sdk import ClaudeAgentOptions, query
from claude_agent_sdk.types import ResultMessage  # 新增

# 用量回報設定
USAGE_REPORT_URL = os.environ.get("USAGE_REPORT_URL", "http://35.229.221.64.nip.io:5000/api/report-usage")
USER_TOKEN = os.environ.get("FWAUTO_USER_TOKEN")  # 用戶的 token


def report_usage_to_server(result_message: ResultMessage, task_type: str):
    """回報用量到中央伺服器"""
    if not USER_TOKEN:
        return  # 沒有設定 token 就跳過

    if not result_message.usage:
        return  # 沒有用量資訊就跳過

    try:
        requests.post(
            USAGE_REPORT_URL,
            headers={"Authorization": f"Bearer {USER_TOKEN}"},
            json={
                "source": "claude_agent_sdk",
                "task_type": task_type,
                "model": "claude-sonnet-4",  # SDK 預設模型
                "input_tokens": result_message.usage.get("input_tokens", 0),
                "output_tokens": result_message.usage.get("output_tokens", 0),
                "total_cost_usd": result_message.total_cost_usd,
                "duration_ms": result_message.duration_ms,
                "num_turns": result_message.num_turns,
            },
            timeout=5
        )
    except Exception:
        pass  # 回報失敗不影響主流程


async def run_ai_brain():
    """Execute AI brain processing with prompt templates."""
    try:
        task_type = analyze_task_type(state)
        prompt = prompt_manager.get_prompt(task_type, dict(state))
        options = get_ai_options(state)

        responses = []
        result_message = None  # 新增：儲存最後的 ResultMessage

        async for message in query(prompt=prompt, options=options):
            logger.debug(f"🧠 AI Brain: {message}")
            print_ai_message(message, prefix="🧠")
            responses.append(str(message))

            # 擷取 ResultMessage（最後一個訊息）
            if isinstance(message, ResultMessage):
                result_message = message

        # 回報用量到伺服器
        if result_message:
            report_usage_to_server(result_message, task_type)

        return {"success": True, "responses": responses, "task_type": task_type}

    except Exception as e:
        logger.debug(f"AI Brain exception: {str(e)}")
        return {"success": False, "error": str(e)}
```

### Flask Server 新增端點

```python
# 在 app.py 新增

@app.route('/api/report-usage', methods=['POST'])
@require_auth
def report_usage():
    """
    接收 Claude Agent SDK 的用量回報
    """
    user = g.current_user
    data = request.json

    db = get_db()
    db.execute('''
        INSERT INTO usage_log
        (user_id, source, task_type, model, input_tokens, output_tokens, cost_usd, duration_ms)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
    ''', (
        user['id'],
        data.get('source', 'claude_agent_sdk'),
        data.get('task_type', 'unknown'),
        data.get('model', 'claude-sonnet-4'),
        data.get('input_tokens', 0),
        data.get('output_tokens', 0),
        data.get('total_cost_usd'),
        data.get('duration_ms')
    ))
    db.commit()

    return jsonify({"status": "ok"})
```

### 更新資料庫 Schema

```sql
-- 擴充 usage_log 表
CREATE TABLE IF NOT EXISTS usage_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL,
    source TEXT DEFAULT 'api',           -- 'api' 或 'claude_agent_sdk'
    task_type TEXT,                       -- 'fix_build_errors', 'implement_feature' 等
    model TEXT NOT NULL,
    input_tokens INTEGER NOT NULL,
    output_tokens INTEGER NOT NULL,
    cost_usd REAL,                        -- SDK 回報的費用
    duration_ms INTEGER,                  -- 執行時間
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);
```

### 用戶環境變數設定

```bash
# 用戶需要設定這個環境變數
export FWAUTO_USER_TOKEN="ut-xxxxxxxxxxxxxxxx"
```

或在 `~/.claude_client_config.json` 中：

```json
{
    "user_token": "ut-xxxxxxxxxxxxxxxx",
    "user_email": "user@example.com"
}
```

然後 ai_brain.py 可以讀取：

```python
def get_user_token():
    """從本地配置讀取 user token"""
    config_file = Path.home() / '.claude_client_config.json'
    if config_file.exists():
        with open(config_file) as f:
            config = json.load(f)
            return config.get('user_token')
    return os.environ.get("FWAUTO_USER_TOKEN")
```

---

## 完整流程圖

```
┌─────────────────────────────────────────────────────────────────────┐
│  用戶執行 fwauto feat "新增 LED 閃爍功能"                            │
└───────────────────────────┬─────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────────────┐
│  ai_brain.py                                                        │
│                                                                     │
│  1. claude_agent_sdk.query() 執行                                   │
│     • 讀取專案檔案                                                   │
│     • 修改程式碼                                                     │
│     • 執行編譯測試                                                   │
│                                                                     │
│  2. 收到 ResultMessage:                                             │
│     {                                                               │
│       "usage": {"input_tokens": 5000, "output_tokens": 2000},       │
│       "total_cost_usd": 0.035,                                      │
│       "duration_ms": 45000                                          │
│     }                                                               │
│                                                                     │
│  3. report_usage_to_server() ──────────────────────────────────────►│
└─────────────────────────────────────────────────────────────────────┘
                                                                      │
                            ┌─────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────────────┐
│  Flask Server                                                       │
│                                                                     │
│  POST /api/report-usage                                             │
│  {                                                                  │
│    "source": "claude_agent_sdk",                                    │
│    "task_type": "implement_feature",                                │
│    "input_tokens": 5000,                                            │
│    "output_tokens": 2000,                                           │
│    "total_cost_usd": 0.035                                          │
│  }                                                                  │
│                                                                     │
│  ──► INSERT INTO usage_log (...)                                    │
└─────────────────────────────────────────────────────────────────────┘
```

---

## 改動摘要

| 項目 | 原本 (LiteLLM) | 改後 |
|------|---------------|------|
| 對話 API | `litellm:4000/v1/messages` | `flask:5000/api/chat` |
| 檔案操作 | 無追蹤 | SDK ResultMessage → `/api/report-usage` |
| 認證 Token | LiteLLM API Key | 統一 User Token |
| 用量追蹤 | 只有對話 | **對話 + 檔案操作 都追蹤** |
| 伺服器數量 | 2 個 (Flask + LiteLLM) | 1 個 (Flask) |

## 優點

1. **減少依賴** - 不需要維護 LiteLLM 伺服器
2. **完整用量追蹤** - 對話 API + Claude Agent SDK 都追蹤
3. **保留檔案操作能力** - ai_brain.py 功能完全不變
4. **更靈活** - 可以加入用量上限、計費邏輯
5. **更簡單** - 只需維護一個 Flask 服務
