fix: drop empty additional_tools input items too

Follow-up Codex turns re-send the additional_tools item with an EMPTY
tools list; the empty item is still a non-standard input variant strict
providers reject, so it must be dropped even with nothing to merge —
keying the rewrite off relocated tools alone made every turn after the
first fail again. (Codex >= 26.707 'Responses Lite'; Codex issue #32086.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Fenghuang 2026-07-22 16:27:10 +09:00
parent c755a81b5e
commit 586f08ff5b
2 changed files with 24 additions and 2 deletions

View file

@ -7478,19 +7478,30 @@ def relocate_input_additional_tools(
the request valid AND lets filter_tools_by_allowed_types apply the
deployment's allowlist to the merged list.
Returns ``(input, tools)``; both unchanged when there is nothing to move.
Follow-up Codex turns re-send the item with an EMPTY tools list (tools
were established earlier in the thread); the empty item is still a
non-standard variant strict providers reject, so it is dropped even when
it carries nothing to merge (Codex >= 26.707 "Responses Lite"
serialization; see Codex issue #32086).
Returns ``(input, tools)``; both unchanged when no additional_tools item
is present.
"""
if not isinstance(input, list):
return input, tools
found = False
relocated: List[dict] = []
kept_items = []
for item in input:
if isinstance(item, dict) and item.get("type") == "additional_tools":
found = True
relocated.extend(item.get("tools") or [])
else:
kept_items.append(item)
if not relocated:
if not found:
return input, tools
if not relocated:
return kept_items, tools
merged = list(tools) if tools is not None else []
merged.extend(relocated)
return kept_items, merged

View file

@ -4976,3 +4976,14 @@ def test_relocate_input_additional_tools() -> None:
# string input passes through
s_input, s_tools = relocate_input_additional_tools(input="hello", tools=None)
assert s_input == "hello" and s_tools is None
# follow-up Codex turns re-send the item with an EMPTY tools list — the
# non-standard item must STILL be dropped (strict providers reject it),
# with the tools param left untouched
turn2_input = [
{"role": "developer", "type": "additional_tools", "tools": []},
{"type": "message", "role": "user", "content": [{"type": "input_text", "text": "next"}]},
]
t2_input, t2_tools = relocate_input_additional_tools(input=turn2_input, tools=None)
assert [i.get("type") for i in t2_input] == ["message"]
assert t2_tools is None