120 lines
4.6 KiB
Python
120 lines
4.6 KiB
Python
"""Transport-only handling of a worker's explicit terminal failure.
|
||
|
||
Never selects a model, evaluates content, or changes worker/Jev decisions.
|
||
If HTTP headers have already gone out, remember the failure for exactly one
|
||
identical retry. Codex then receives an HTTP error instead of re-running the
|
||
whole exhausted provider chain. Ordinary network interruptions remain retries.
|
||
"""
|
||
import hashlib
|
||
import json
|
||
import threading
|
||
import time
|
||
from collections import OrderedDict
|
||
|
||
|
||
def request_key(request, headers):
|
||
principal = "\0".join(headers.get(name, "") for name in
|
||
("Authorization", "ChatGPT-Account-Id", "session_id"))
|
||
body = json.dumps(request, sort_keys=True, ensure_ascii=False, separators=(",", ":"))
|
||
return hashlib.sha256((principal + "\0" + body).encode()).digest()
|
||
|
||
|
||
class FailureReplays:
|
||
def __init__(self, ttl=120, capacity=128, clock=time.monotonic):
|
||
self.ttl, self.capacity, self.clock = ttl, capacity, clock
|
||
self.items = OrderedDict()
|
||
self.lock = threading.Lock()
|
||
|
||
def remember(self, key, error):
|
||
with self.lock:
|
||
self.items[key] = (self.clock() + self.ttl, error)
|
||
self.items.move_to_end(key)
|
||
while len(self.items) > self.capacity:
|
||
self.items.popitem(last=False)
|
||
|
||
def take(self, key):
|
||
with self.lock:
|
||
entry = self.items.pop(key, None)
|
||
return entry[1] if entry and entry[0] > self.clock() else None
|
||
|
||
|
||
def error_body(error):
|
||
# Preserve the actual provider failure; don't relabel it invalid_prompt,
|
||
# fabricate response.completed, or expose a rejected model response.
|
||
return {"error": {"type": "upstream_terminal_error", "code": error["code"],
|
||
"message": "Model yanıtı hata ile sonlandı. " + error["message"]}}
|
||
|
||
|
||
class Observer:
|
||
"""Bounded SSE observer. Forwarded stream bytes are never rewritten."""
|
||
LIMIT = 65536
|
||
|
||
def __init__(self):
|
||
self.tail = b""
|
||
self.data = []
|
||
self.size = 0
|
||
self.discard = False
|
||
self.discard_line = False
|
||
self.comment_seen = False
|
||
self.payload_seen = False
|
||
self.completed = False
|
||
self.error = None
|
||
|
||
def feed(self, chunk):
|
||
# read1() supplies small chunks, but also bound arbitrary caller input.
|
||
for offset in range(0, len(chunk), 4096):
|
||
self.tail += chunk[offset:offset + 4096]
|
||
while b"\n" in self.tail:
|
||
line, self.tail = self.tail.split(b"\n", 1)
|
||
if self.discard_line:
|
||
self.discard_line = False
|
||
continue
|
||
self._line(line.rstrip(b"\r"))
|
||
if len(self.tail) > self.LIMIT:
|
||
self.tail = b""
|
||
self.discard = self.discard_line = True
|
||
|
||
def _line(self, line):
|
||
if line.startswith(b":"):
|
||
self.comment_seen = True
|
||
elif line.startswith(b"data:"):
|
||
self.size += len(line)
|
||
if self.size > self.LIMIT:
|
||
self.discard = True
|
||
self.data.clear()
|
||
elif not self.discard:
|
||
self.data.append(line[5:].lstrip())
|
||
elif not line:
|
||
if self.data and not self.discard:
|
||
self._event(b"\n".join(self.data))
|
||
elif self.discard:
|
||
self.payload_seen = True
|
||
self.data, self.size, self.discard = [], 0, False
|
||
|
||
def _event(self, data):
|
||
try:
|
||
event = json.loads(data)
|
||
except (ValueError, UnicodeError):
|
||
self.payload_seen = True
|
||
return
|
||
if not isinstance(event, dict):
|
||
self.payload_seen = True
|
||
return
|
||
kind = event.get("type")
|
||
response = event.get("response")
|
||
error = event.get("error") if kind == "error" else response.get("error") if isinstance(response, dict) else None
|
||
# Known worker terminal errors only. Preserve context/rate-limit and
|
||
# other native error classifications rather than guessing semantics.
|
||
is_failure = (kind == "error" and isinstance(error, dict)
|
||
and error.get("type") == "upstream_error")
|
||
is_failure |= (kind == "response.failed" and isinstance(error, dict)
|
||
and error.get("code") == "jev_no_fallback")
|
||
if is_failure:
|
||
self.error = {"code": str(error.get("code") or "upstream_error")[:100],
|
||
"message": str(error.get("message") or "Sağlayıcı yanıtı tamamlanamadı.")[:2000]}
|
||
else:
|
||
self.payload_seen = True
|
||
if kind == "response.completed":
|
||
self.completed = True
|
||
self.error = None
|