-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
171 lines (131 loc) · 5.14 KB
/
main.py
File metadata and controls
171 lines (131 loc) · 5.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import os
import random
import time
import logging
import httpx
from fastapi import FastAPI, Request, Response
from fastapi.responses import StreamingResponse
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("oc-proxy")
UPSTREAM_URL = os.environ.get("UPSTREAM_URL", "https://opencode.ai/zen/go/v1")
MAX_RETRIES = int(os.environ.get("MAX_RETRIES", "3"))
CROCKFORD_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
app = FastAPI()
def encode_base32(value: int, length: int = 10) -> str:
result = ""
for _ in range(length):
result = CROCKFORD_ALPHABET[value & 31] + result
value >>= 5
return result
def random_base32(length: int = 17) -> str:
return "".join(CROCKFORD_ALPHABET[random.randint(0, 31)] for _ in range(length))
def generate_session_id() -> str:
ts = int(time.time() * 1000)
encoded = encode_base32(0xFFFFFFFFFFFF - ts)
return f"ses_{encoded}{random_base32()}"
def generate_request_id() -> str:
ts = int(time.time() * 1000)
encoded = encode_base32(ts)
return f"msg_{encoded}{random_base32()}"
session_id = generate_session_id()
logger.info(f"Initial session ID: {session_id}")
def regenerate_credentials():
global session_id
session_id = generate_session_id()
logger.info(f"Regenerated session ID: {session_id}")
@app.post("/v1/chat/completions")
async def proxy_chat(request: Request):
return await _do_proxy(request, "chat/completions")
@app.get("/v1/models")
async def proxy_models(request: Request):
response = await _do_proxy(request, "models")
if response.status_code == 200:
import json
data = json.loads(response.body)
data["data"] = [m for m in data.get("data", []) if "free" in m.get("id", "").lower()]
return Response(
content=json.dumps(data),
status_code=200,
headers=dict(response.headers),
media_type="application/json",
)
return response
async def _do_proxy(request: Request, path: str):
global session_id
body = await request.body()
headers = dict(request.headers)
headers.pop("host", None)
headers.pop("content-length", None)
headers.pop("accept-encoding", None)
target_url = f"{UPSTREAM_URL}/{path}"
if request.url.query:
target_url += f"?{request.url.query}"
is_stream = b'"stream":true' in body or b'"stream": true' in body
for attempt in range(MAX_RETRIES):
request_id = generate_request_id()
req_headers = {
**headers,
"x-opencode-session": session_id,
"x-opencode-request": request_id,
}
logger.info(f"Attempt {attempt + 1}/{MAX_RETRIES} -> {request.method} {target_url}")
if is_stream:
client = httpx.AsyncClient(timeout=300.0)
req = client.build_request(
method=request.method,
url=target_url,
headers=req_headers,
content=body,
)
response = await client.send(req, stream=True)
if response.status_code == 429 or response.status_code >= 500:
await response.aclose()
await client.aclose()
logger.warning(f"Got {response.status_code}, regenerating credentials...")
regenerate_credentials()
continue
resp_headers = dict(response.headers)
resp_headers.pop("content-length", None)
resp_headers.pop("transfer-encoding", None)
resp_headers.pop("content-encoding", None)
async def stream_response():
try:
async for chunk in response.aiter_bytes():
yield chunk
finally:
await response.aclose()
await client.aclose()
return StreamingResponse(
stream_response(),
status_code=response.status_code,
headers=resp_headers,
)
else:
async with httpx.AsyncClient(timeout=300.0) as client:
response = await client.request(
method=request.method,
url=target_url,
headers=req_headers,
content=body,
)
if response.status_code == 429 or response.status_code >= 500:
logger.warning(f"Got {response.status_code}, regenerating credentials...")
regenerate_credentials()
continue
resp_headers = dict(response.headers)
resp_headers.pop("content-length", None)
resp_headers.pop("transfer-encoding", None)
resp_headers.pop("content-encoding", None)
return Response(
content=response.content,
status_code=response.status_code,
headers=resp_headers,
)
logger.error("All retries exhausted")
return Response(content=b'{"error": "all retries exhausted"}', status_code=502)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8080)
def run():
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8080)