|
|
|
|
|
"""FastAPI mock server for debugging proxy/client behavior. |
|
|
|
|
|
This is a development helper (not used by the main KiroProxy app). |
|
|
|
|
|
Typical usage: |
|
|
python scripts/proxy_server.py --port 8000 |
|
|
|
|
|
Then point your client/proxy to `http://127.0.0.1:8000` and inspect requests: |
|
|
GET http://127.0.0.1:8000/logs |
|
|
""" |
|
|
|
|
|
from __future__ import annotations |
|
|
|
|
|
import argparse |
|
|
import json |
|
|
import logging |
|
|
from datetime import datetime |
|
|
from typing import Any, Dict, List |
|
|
|
|
|
import uvicorn |
|
|
from fastapi import FastAPI, Request |
|
|
from fastapi.responses import JSONResponse |
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") |
|
|
logger = logging.getLogger("kiro_proxy.mock_server") |
|
|
|
|
|
app = FastAPI(title="KiroProxy Mock Server") |
|
|
request_log: List[Dict[str, Any]] = [] |
|
|
|
|
|
|
|
|
@app.middleware("http") |
|
|
async def log_requests(request: Request, call_next): |
|
|
body = await request.body() |
|
|
|
|
|
request_log.append( |
|
|
{ |
|
|
"timestamp": datetime.now().isoformat(), |
|
|
"method": request.method, |
|
|
"url": str(request.url), |
|
|
"path": request.url.path, |
|
|
"headers": dict(request.headers), |
|
|
"body": body.decode("utf-8", errors="ignore")[:2000] if body else None, |
|
|
} |
|
|
) |
|
|
|
|
|
logger.info("%s %s", request.method, request.url.path) |
|
|
return await call_next(request) |
|
|
|
|
|
|
|
|
@app.get("/") |
|
|
async def root(): |
|
|
return {"status": "ok", "message": "Mock server running", "requests_logged": len(request_log)} |
|
|
|
|
|
|
|
|
@app.get("/logs") |
|
|
async def get_logs(limit: int = 50): |
|
|
return {"total": len(request_log), "requests": request_log[-limit:]} |
|
|
|
|
|
|
|
|
@app.get("/clear") |
|
|
async def clear_logs(): |
|
|
request_log.clear() |
|
|
return {"message": "Logs cleared"} |
|
|
|
|
|
|
|
|
@app.api_route("/auth/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"]) |
|
|
async def mock_auth(_: Request, path: str): |
|
|
logger.info("Auth request: %s", path) |
|
|
return JSONResponse({"success": True, "token": "mock-token-for-testing", "expires_in": 3600}) |
|
|
|
|
|
|
|
|
@app.post("/v1/chat/completions") |
|
|
async def mock_chat_completions(request: Request): |
|
|
body = await request.json() |
|
|
logger.info("Chat body preview: %s", json.dumps(body, ensure_ascii=False)[:500]) |
|
|
|
|
|
return JSONResponse( |
|
|
{ |
|
|
"id": "chatcmpl-test", |
|
|
"object": "chat.completion", |
|
|
"created": int(datetime.now().timestamp()), |
|
|
"model": body.get("model", "mock-model"), |
|
|
"choices": [ |
|
|
{ |
|
|
"index": 0, |
|
|
"message": {"role": "assistant", "content": "Mock response from scripts/proxy_server.py"}, |
|
|
"finish_reason": "stop", |
|
|
} |
|
|
], |
|
|
"usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30}, |
|
|
} |
|
|
) |
|
|
|
|
|
|
|
|
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"]) |
|
|
async def catch_all(request: Request, path: str): |
|
|
logger.info("Caught: %s /%s", request.method, path) |
|
|
return JSONResponse( |
|
|
{ |
|
|
"proxy_status": "intercepted", |
|
|
"method": request.method, |
|
|
"path": f"/{path}", |
|
|
"message": "Request intercepted by mock server", |
|
|
"headers_received": dict(request.headers), |
|
|
} |
|
|
) |
|
|
|
|
|
|
|
|
def _parse_args() -> argparse.Namespace: |
|
|
parser = argparse.ArgumentParser(description="KiroProxy mock server (dev helper)") |
|
|
parser.add_argument("--host", default="0.0.0.0", help="Bind host") |
|
|
parser.add_argument("--port", type=int, default=8000, help="Bind port") |
|
|
return parser.parse_args() |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
args = _parse_args() |
|
|
uvicorn.run(app, host=args.host, port=args.port) |
|
|
|