fix(codex-bridge): flatten top-level oneOf/anyOf/allOf in tool schemas for external models

Since bridge 7 external models get Codex's classic tools. One desktop tool has a top-level union schema,
which Claude rejects ("input_schema does not support oneOf, allOf, or anyOf at the top level"), so every
Opus request from the desktop app failed with a bare "upstream 400". The earlier context-size diagnosis
was wrong: the request still failed after compaction (239 KB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
yilsemandClaude Opus 5.5 committed 2026-10-11 01:08:27 +03:00
1 parent 1445e204da
commit 1e2b8e2b9c
4 files changed
+68 -3

No files matched your search

@@ -281,6 +281,48 @@ SLIM_CLONES = bool(CFG.get("slim_clones", False))
SLIM_FIELDS = CFG.get("slim_fields", ["model_messages", "base_instructions"])
# ---- Codex'in yeni arac bicimini duz 'tools' listesine acma (9Router gibi ceviricilerin anlamasi icin) ----
def sema_duzelt(sema):
"""Arac parametre semasinin EN UST duzeyindeki oneOf/anyOf/allOf'u tek nesne semasina indirir.
Claude bunu reddediyor ("input_schema does not support oneOf, allOf, or anyOf at the top level"; olcum
11 Ekim 2026: masaustu Codex'in klasik arac listesindeki bir arac yuzunden her Opus istegi 400 aldi).
Secenek nesnelerinin alanlari birlestirilir; zorunlu alanlar oneOf/anyOf'ta kesisim, allOf'ta birlesimdir.
Donus: (sema, degisti mi)."""
if not isinstance(sema, dict) or not any(k in sema for k in ("oneOf", "anyOf", "allOf")):
return sema, False
yeni = {k: v for k, v in sema.items() if k not in ("oneOf", "anyOf", "allOf")}
alanlar = dict(yeni.get("properties") or {})
zorunlu = set(yeni.get("required") or [])
for anahtar in ("oneOf", "anyOf", "allOf"):
secenekler = [x for x in (sema.get(anahtar) or []) if isinstance(x, dict)]
kesisim = None
for secenek in secenekler:
secenek, _ = sema_duzelt(secenek)
for ad, tanim in (secenek.get("properties") or {}).items():
alanlar.setdefault(ad, tanim)
gerekli = set(secenek.get("required") or [])
if anahtar == "allOf":
zorunlu |= gerekli
else:
kesisim = gerekli if kesisim is None else (kesisim & gerekli)
if anahtar != "allOf" and kesisim:
zorunlu |= kesisim
yeni["type"] = "object"
yeni["properties"] = alanlar
if zorunlu:
yeni["required"] = sorted(a for a in zorunlu if a in alanlar)
else:
yeni.pop("required", None)
return yeni, True
def arac_semalarini_duzelt(req):
"""Dis modele giden duz arac listesinde ust duzey birlesimli semalari duzeltir. Donus: duzeltilen arac sayisi."""
n = 0
for t in req.get("tools") or []:
if isinstance(t, dict) and isinstance(t.get("parameters"), dict):
t["parameters"], degisti = sema_duzelt(t["parameters"])
n += bool(degisti)
return n
def flatten_request(req):
"""additional_tools/namespace/custom -> duz function tools. Donus: custom arac adlari kumesi."""
custom = set()
@@ -1238,6 +1280,9 @@ class H(http.server.BaseHTTPRequestHandler):
rewriter = None
if flat if flat is not None else route.get("flatten_tools"):
custom = flatten_request(req)
duzelen = arac_semalarini_duzelt(req)
if duzelen:
log(f"{tag} {duzelen} aracin semasi duzeltildi (ust duzey oneOf/anyOf/allOf)")
rewriter = SseRewriter(custom, hedef=target, tasma=self._tasma_ogren, aracsiz=self._aracsiz_ogren)
fix_tool_ids(req)
if via_router and CFG.get('resim_sadelestir', True):