-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathremote_codex.py
More file actions
144 lines (131 loc) · 6.12 KB
/
Copy pathremote_codex.py
File metadata and controls
144 lines (131 loc) · 6.12 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
"""Stateless client for the internal, PVC-owning Codex broker.
The public router replicas must not mount or copy ChatGPT refresh credentials.
They send only the already-normalized provider request over the cluster network;
the broker selects an account, performs the call, and returns the canonical
router result plus sanitized quota signals.
"""
from __future__ import annotations
import json
import time
from typing import Any, Awaitable, Callable
import httpx
from provider_adapters.common import _err
Emit = Callable[[str], Awaitable[None]]
class RemoteCodexClient:
def __init__(self, base_url: str, token: str, *,
timeout_s: float = 120.0, client: Any = None):
base_url = str(base_url or "").strip().rstrip("/")
token = str(token or "").strip()
if not base_url:
raise ValueError("CODEX_BROKER_URL must not be empty")
if not token:
raise ValueError("CODEX_BROKER_TOKEN must not be empty")
self.base_url = base_url
self.timeout_s = max(1.0, float(timeout_s))
self._headers = {"authorization": f"Bearer {token}"}
self._owns_client = client is None
self._client = client or httpx.AsyncClient(
limits=httpx.Limits(max_connections=100,
max_keepalive_connections=40))
def _timeout(self, request: dict) -> float:
requested = request.get("timeout_ms")
if isinstance(requested, (int, float)) and requested > 0:
return max(1.0, float(requested) / 1000.0 + 5.0)
return self.timeout_s + 5.0
@staticmethod
def _replay_signals(payload: Any, observe) -> None:
if observe is None or not isinstance(payload, list):
return
for raw in payload[:16]:
if not isinstance(raw, dict):
continue
headers = raw.get("headers")
signal = {
"status": int(raw.get("status") or 0),
"headers": {
str(k)[:100]: str(v)[:1000]
for k, v in (headers.items()
if isinstance(headers, dict) else [])
},
"ts": int(raw.get("ts") or time.time()),
}
try:
observe(signal)
except Exception:
pass
@staticmethod
def _http_error(status: int, detail: str) -> dict:
if status in (401, 403):
kind = "auth_error"
elif status == 429:
kind = "rate_limit"
else:
kind = "server_error"
return _err(kind, status, 0,
f"Codex broker returned {status}: {detail[:300]}")
async def call(self, request: dict, *, observe=None) -> dict:
try:
response = await self._client.post(
f"{self.base_url}/v1/call", json=request,
headers=self._headers, timeout=self._timeout(request))
except httpx.TimeoutException:
return _err("timeout", 0, 0, "Codex broker timed out")
except (httpx.NetworkError, httpx.RequestError) as exc:
return _err("network_error", 0, 0,
f"Codex broker unavailable: {exc}")
if response.status_code != 200:
return self._http_error(response.status_code, response.text)
try:
payload = response.json()
except (ValueError, json.JSONDecodeError) as exc:
return _err("bad_response", 200, 0,
f"invalid Codex broker response: {exc}")
if not isinstance(payload, dict) or not isinstance(payload.get("result"), dict):
return _err("bad_response", 200, 0,
"Codex broker response has no result")
self._replay_signals(payload.get("signals"), observe)
return payload["result"]
async def stream(self, request: dict, emit: Emit, *, observe=None) -> dict:
emitted = False
try:
async with self._client.stream(
"POST", f"{self.base_url}/v1/stream", json=request,
headers=self._headers,
timeout=self._timeout(request)) as response:
if response.status_code != 200:
detail = (await response.aread()).decode(
"utf-8", "replace")[:500]
return self._http_error(response.status_code, detail)
async for line in response.aiter_lines():
if not line:
continue
try:
event = json.loads(line)
except ValueError:
return _err("stream_interrupted" if emitted else "bad_response",
200, 0, "invalid Codex broker stream event")
if not isinstance(event, dict):
continue
if event.get("event") == "delta":
delta = event.get("delta")
if isinstance(delta, str) and delta:
await emit(delta)
emitted = True
elif event.get("event") == "result":
result = event.get("result")
if not isinstance(result, dict):
return _err("bad_response", 200, 0,
"Codex broker stream has invalid result")
self._replay_signals(event.get("signals"), observe)
return result
except httpx.TimeoutException:
return _err("stream_interrupted" if emitted else "timeout", 0, 0,
"Codex broker stream timed out")
except (httpx.NetworkError, httpx.RequestError) as exc:
return _err("stream_interrupted" if emitted else "network_error", 0, 0,
f"Codex broker stream unavailable: {exc}")
return _err("stream_interrupted" if emitted else "bad_response", 200, 0,
"Codex broker stream ended without a result")
async def aclose(self) -> None:
if self._owns_client:
await self._client.aclose()