Samiran757 commited on
Commit
082d3d5
·
verified ·
1 Parent(s): 4647a49

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +98 -0
app.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import httpx
3
+ import asyncio
4
+ import json
5
+ import time
6
+ from fastapi import FastAPI, Request, HTTPException
7
+ from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
8
+
9
+ app = FastAPI(title="Ciel Proxy Router")
10
+
11
+ # Config from env vars
12
+ UPSTREAM_URL = os.getenv("UPSTREAM_URL", "https://agentrouter.org")
13
+ API_KEY = os.getenv("API_KEY", "")
14
+ PROXY_URL = os.getenv("PROXY_URL", "")
15
+
16
+ async def get_proxy_client():
17
+ if PROXY_URL:
18
+ return httpx.AsyncClient(proxy=PROXY_URL, timeout=httpx.Timeout(300.0, connect=30.0))
19
+ return httpx.AsyncClient(timeout=httpx.Timeout(300.0, connect=30.0))
20
+
21
+ @app.get("/")
22
+ async def root():
23
+ return HTMLResponse(DARK_HTML)
24
+
25
+ @app.get("/health")
26
+ async def health():
27
+ return {"status": "ok", "upstream": UPSTREAM_URL, "proxy": bool(PROXY_URL)}
28
+
29
+ @app.api_route("/v1/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
30
+ async def proxy_request(path: str, request: Request):
31
+ body = await request.body()
32
+ headers = dict(request.headers)
33
+ headers.pop("host", None)
34
+ headers.pop("content-length", None)
35
+
36
+ if API_KEY:
37
+ headers["authorization"] = f"Bearer {API_KEY}"
38
+
39
+ target_url = f"{UPSTREAM_URL}/v1/{path}"
40
+
41
+ client = await get_proxy_client()
42
+ try:
43
+ req = client.build_request(
44
+ method=request.method,
45
+ url=target_url,
46
+ headers=headers,
47
+ content=body,
48
+ )
49
+ resp = await client.send(req, stream=True)
50
+
51
+ resp_headers = dict(resp.headers)
52
+ resp_headers.pop("content-encoding", None)
53
+ resp_headers.pop("transfer-encoding", None)
54
+ resp_headers.pop("content-length", None)
55
+
56
+ async def stream():
57
+ async for chunk in resp.aiter_bytes():
58
+ yield chunk
59
+ await resp.aclose()
60
+ await client.aclose()
61
+
62
+ return StreamingResponse(stream(), status_code=resp.status_code, headers=resp_headers)
63
+ except Exception as e:
64
+ await client.aclose()
65
+ return JSONResponse({"error": str(e)}, status_code=502)
66
+
67
+ DARK_HTML = """<!DOCTYPE html>
68
+ <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
69
+ <title>Ciel Proxy Router</title>
70
+ <style>
71
+ *{margin:0;padding:0;box-sizing:border-box}
72
+ body{background:#0d1117;color:#c9d1d9;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center}
73
+ .card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:40px;max-width:600px;width:90%}
74
+ h1{color:#58a6ff;font-size:24px;margin-bottom:8px}
75
+ .sub{color:#8b949e;font-size:14px;margin-bottom:24px}
76
+ .status{display:flex;align-items:center;gap:8px;margin-bottom:16px}
77
+ .dot{width:10px;height:10px;border-radius:50%;background:#3fb950;animation:pulse 2s infinite}
78
+ @keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
79
+ .info{background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:16px;margin-top:16px}
80
+ .info p{color:#8b949e;font-size:13px;margin:4px 0}
81
+ .info code{color:#79c0ff;background:#1f2937;padding:2px 6px;border-radius:4px;font-size:12px}
82
+ .footer{color:#484f58;font-size:11px;margin-top:24px;text-align:center}
83
+ </style></head><body>
84
+ <div class="card">
85
+ <h1>⚡ Ciel Proxy Router</h1>
86
+ <p class="sub">Transparent proxy for Claude Code CLI</p>
87
+ <div class="status"><span class="dot"></span><span>Online</span></div>
88
+ <div class="info">
89
+ <p>Usage in Claude Code:</p>
90
+ <p><code>ANTHROPIC_BASE_URL=https://your-space.hf.space</code></p>
91
+ <p><code>ANTHROPIC_API_KEY=your-key</code></p>
92
+ </div>
93
+ <p class="footer">Built by Ciel • Naruto859</p>
94
+ </div></body></html>"""
95
+
96
+ if __name__ == "__main__":
97
+ import uvicorn
98
+ uvicorn.run(app, host="0.0.0.0", port=7860)