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:
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):
|
||||
|
||||
Reference in new issue
Block a user