fix(bridge): stop replaying explicit terminal provider failures

This commit is contained in:
yilsem committed 2026-10-13 17:18:38 +03:00
1 parent 4037c65204
commit 4924781e2d
3 files changed
+313

No files matched your search

@@ -32,6 +32,7 @@ import time
import urllib.error
import urllib.request
import gecis_context
import terminal_stream
HERE = os.path.dirname(os.path.abspath(__file__))
SURUM = "17" # AITURK IDE'nin bekledigi kopru surumu (codex-link-config.ts CODEX_BRIDGE_VERSION)
@@ -348,6 +349,36 @@ class H(http.server.BaseHTTPRequestHandler):
self.end_headers()
self.wfile.write(hata)
return True
observer = None
first_chunks = []
failure_key = getattr(self, "_failure_key", None)
if (hedef == KOPRU and failure_key is not None and durum == 200
and "text/event-stream" in yanit.headers.get("Content-Type", "")
and yanit.headers.get("Content-Encoding", "identity") == "identity"):
observer = terminal_stream.Observer()
if not started:
# Inspect only until the first SSE frame/comment. Keep normal
# streaming and heartbeats; an error-only response can still
# return a truthful HTTP failure before headers are committed.
try:
while True:
first = yanit.read1(4096)
if not first:
break
first_chunks.append(first)
observer.feed(first)
if observer.error and not observer.payload_seen:
yanit.close()
self._json(400, terminal_stream.error_body(observer.error))
return True
if observer.comment_seen or observer.payload_seen or sum(map(len, first_chunks)) >= observer.LIMIT:
break
except (OSError, http.client.HTTPException):
# A transport interruption without an explicit provider
# failure keeps the client's existing recovery behavior.
yanit.close()
self.close_connection = True
return True
if not started:
self.send_response(durum)
for k, v in yanit.headers.items():
@@ -359,6 +390,10 @@ class H(http.server.BaseHTTPRequestHandler):
event_tail = b""
completed = False
try:
for first in first_chunks:
toplam += len(first)
self.wfile.write(b"%x\r\n" % len(first) + first + b"\r\n")
self.wfile.flush()
while True:
try:
parca = yanit.read1(4096) # gelen kadarini hemen verir; bekletmez
@@ -370,6 +405,8 @@ class H(http.server.BaseHTTPRequestHandler):
if not parca:
break
toplam += len(parca)
if observer is not None:
observer.feed(parca)
if getattr(self, "_native_return_scope", None):
event_tail += parca
while b"\n" in event_tail:
@@ -382,6 +419,9 @@ class H(http.server.BaseHTTPRequestHandler):
pass
self.wfile.write(b"%x\r\n" % len(parca) + parca + b"\r\n")
self.wfile.flush()
if observer is not None and observer.error and not observer.completed:
self.server.failure_replays.remember(failure_key, observer.error)
log(f"{tag} terminal worker error; identical automatic replay will return HTTP 400")
self.wfile.write(b"0\r\n\r\n")
self.wfile.flush()
except (ConnectionAbortedError, BrokenPipeError, ConnectionResetError):
@@ -453,6 +493,7 @@ class H(http.server.BaseHTTPRequestHandler):
def _isle(self):
self._native_return_scope = None
self._failure_key = None
with kilit:
sira[0] += 1
n = sira[0]
@@ -479,6 +520,12 @@ class H(http.server.BaseHTTPRequestHandler):
req = {}
except Exception:
req = {}
if external:
self._failure_key = terminal_stream.request_key(req or {"raw": govde.hex()}, self.headers)
previous_failure = self.server.failure_replays.take(self._failure_key)
if previous_failure:
log(f"[{n} {model}] terminal worker error replay stopped before upstream request")
return self._json(400, terminal_stream.error_body(previous_failure))
thread = str(req.get("prompt_cache_key") or "")
scope = hashlib.sha256((str(self.headers.get("chatgpt-account-id") or "") + "|" + thread).encode()).hexdigest()
source = TRANSFERS.get(scope) if thread else None
@@ -541,6 +588,10 @@ class H(http.server.BaseHTTPRequestHandler):
class Sunucu(http.server.ThreadingHTTPServer):
def __init__(self, *args, **kwargs):
self.failure_replays = terminal_stream.FailureReplays()
super().__init__(*args, **kwargs)
daemon_threads = True
# Shipped Python 3.11 defaults to only five pending connections. Model
# discovery, voice and simultaneous chats can arrive in the same burst.