diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dea4a9..1c6501e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v3.11.10 (2026-05-26) + +**Antigravity Fix: Interleave function_call/output Pairs, Gemini Turn Trimming (PR #11)** + +### Bug Fixes +- **Fix Antigravity function_call/output ordering**: Tool calls and their responses are now properly interleaved in sequence (`function_call` → `function_call_output` → `function_call` → ...) instead of being grouped separately +- **Gemini sanitizer trimming**: Leading/trailing non-user turns removed for Google API compliance (Google requires conversation to start and end with user turn) +- **Stricter role boundary enforcement**: `functionCall` (model) and `functionResponse` (user) never merged across role boundaries +- **Merge PR #11**: Fix by qwen-chat coder + +## v3.11.9 (2026-05-26) + ## v3.11.9 (2026-05-26) **Antigravity Fix: Preserve functionCall/functionResponse in Gemini Sanitizer (PR #10)** diff --git a/codex-launcher_3.11.10_all.deb b/codex-launcher_3.11.10_all.deb new file mode 100644 index 0000000..f142b5e Binary files /dev/null and b/codex-launcher_3.11.10_all.deb differ diff --git a/codex-launcher_3.11.9_all.deb b/codex-launcher_3.11.9_all.deb deleted file mode 100644 index 92d0edc..0000000 Binary files a/codex-launcher_3.11.9_all.deb and /dev/null differ diff --git a/src/codex-launcher-gui b/src/codex-launcher-gui index 8ee57fc..0c046a5 100755 --- a/src/codex-launcher-gui +++ b/src/codex-launcher-gui @@ -27,6 +27,10 @@ model_catalog_json = "" """ CHANGELOG = [ + ("3.11.10", "2026-05-26", [ + "Fix Antigravity: interleave function_call/output pairs (PR #11)", + "Gemini sanitizer: trim non-user turns for Google API compliance", + ]), ("3.11.9", "2026-05-26", [ "Fix Antigravity: preserve functionCall/functionResponse (PR #10)", "Prevents tool responses from being dropped in multi-turn sessions", diff --git a/src/codex_launcher_lib.py b/src/codex_launcher_lib.py index 309df99..6b2b1be 100644 --- a/src/codex_launcher_lib.py +++ b/src/codex_launcher_lib.py @@ -83,6 +83,11 @@ model_catalog_json = "" """ CHANGELOG = [ + ("3.11.10", "2026-05-26", [ + "Fix Antigravity: interleave function_call/output pairs in correct sequence (PR #11)", + "Fix Gemini sanitizer: trim leading/trailing non-user turns for Google API compliance", + "Stricter function call/response isolation — no merging across role boundaries", + ]), ("3.11.9", "2026-05-26", [ "Fix Antigravity: preserve functionCall/functionResponse in Gemini sanitizer (PR #10)", "Prevents tool responses from being merged/dropped in multi-turn Antigravity sessions", diff --git a/src/translate-proxy.py b/src/translate-proxy.py index 057c6c2..d2396ef 100755 --- a/src/translate-proxy.py +++ b/src/translate-proxy.py @@ -4958,6 +4958,7 @@ def _antigravity_normalize_context(input_data, model=""): if cid in tool_call_ids: paired_calls.append((idx, item)) + # Build result maintaining proper function_call -> function_call_output pairing result = list(dev_messages) compaction_summaries = [] @@ -4973,11 +4974,30 @@ def _antigravity_normalize_context(input_data, model=""): summary_text = f"[Tool history summary: {n_summarized} older tool outputs omitted. {n_tool_calls} prior function calls were made for file inspection/editing.]" result.append({"type": "message", "role": "user", "content": [{"type": "input_text", "text": summary_text}]}) - for _, call_item in paired_calls: - result.append(call_item) - + # CRITICAL FIX: Interleave function_calls with their corresponding function_call_outputs + # to maintain the required sequence: function_call -> function_call_output -> function_call -> ... + # Build a lookup map: call_id -> function_call_output item + tool_output_map = {} for _, tool_item in kept_tools: - result.append(tool_item) + cid = tool_item.get("call_id", tool_item.get("id", "")) + if cid: + tool_output_map[cid] = tool_item + + # First, add all paired function_calls followed immediately by their responses + added_call_ids = set() + for _, call_item in paired_calls: + cid = call_item.get("call_id", call_item.get("id", "")) + result.append(call_item) + added_call_ids.add(cid) + # Immediately append the corresponding function_call_output if available + if cid in tool_output_map: + result.append(tool_output_map[cid]) + + # Add any remaining tool outputs that weren't paired (orphans) + for _, tool_item in kept_tools: + cid = tool_item.get("call_id", tool_item.get("id", "")) + if cid not in added_call_ids: + result.append(tool_item) for cs_item in compaction_summaries: result.append(cs_item) @@ -5569,17 +5589,21 @@ class Handler(http.server.BaseHTTPRequestHandler): # Skip duplicate user text messages, but NEVER skip function responses if role == "user" and text_key and text_key == last_user_text and not has_function_response: continue - # Only merge same-role messages if they don't contain function calls/responses - # Function calls and responses must remain as separate turns + # CRITICAL FIX: Function calls (model role) and function responses (user role) MUST NOT be merged + # Google's API requires strict alternation: functionCall (model) -> functionResponse (user) + # Never merge across role boundaries when function calls/responses are involved if role == last_role and role in ("user", "model") and sanitized and not has_function_call and not has_function_response: + # Only merge same-role consecutive text messages without tool content sanitized[-1].setdefault("parts", []).extend(parts) else: sanitized.append({"role": role, "parts": parts}) if role == "user" and text_key: last_user_text = text_key last_role = role + # Trim leading non-user messages (Google expects conversation to start with user) while sanitized and sanitized[0].get("role") != "user": sanitized.pop(0) + # Trim trailing non-user messages (must end with user turn for continuation) while sanitized and sanitized[-1].get("role") != "user": sanitized.pop() contents = sanitized