v3.11.10: fix Antigravity — interleave function_call/output pairs, trim Gemini turns (PR #11)
This commit is contained in:
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,5 +1,17 @@
|
|||||||
# Changelog
|
# 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)
|
## v3.11.9 (2026-05-26)
|
||||||
|
|
||||||
**Antigravity Fix: Preserve functionCall/functionResponse in Gemini Sanitizer (PR #10)**
|
**Antigravity Fix: Preserve functionCall/functionResponse in Gemini Sanitizer (PR #10)**
|
||||||
|
|||||||
BIN
codex-launcher_3.11.10_all.deb
Normal file
BIN
codex-launcher_3.11.10_all.deb
Normal file
Binary file not shown.
Binary file not shown.
@@ -27,6 +27,10 @@ model_catalog_json = ""
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
CHANGELOG = [
|
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", [
|
("3.11.9", "2026-05-26", [
|
||||||
"Fix Antigravity: preserve functionCall/functionResponse (PR #10)",
|
"Fix Antigravity: preserve functionCall/functionResponse (PR #10)",
|
||||||
"Prevents tool responses from being dropped in multi-turn sessions",
|
"Prevents tool responses from being dropped in multi-turn sessions",
|
||||||
|
|||||||
@@ -83,6 +83,11 @@ model_catalog_json = ""
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
CHANGELOG = [
|
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", [
|
("3.11.9", "2026-05-26", [
|
||||||
"Fix Antigravity: preserve functionCall/functionResponse in Gemini sanitizer (PR #10)",
|
"Fix Antigravity: preserve functionCall/functionResponse in Gemini sanitizer (PR #10)",
|
||||||
"Prevents tool responses from being merged/dropped in multi-turn Antigravity sessions",
|
"Prevents tool responses from being merged/dropped in multi-turn Antigravity sessions",
|
||||||
|
|||||||
@@ -4958,6 +4958,7 @@ def _antigravity_normalize_context(input_data, model=""):
|
|||||||
if cid in tool_call_ids:
|
if cid in tool_call_ids:
|
||||||
paired_calls.append((idx, item))
|
paired_calls.append((idx, item))
|
||||||
|
|
||||||
|
# Build result maintaining proper function_call -> function_call_output pairing
|
||||||
result = list(dev_messages)
|
result = list(dev_messages)
|
||||||
|
|
||||||
compaction_summaries = []
|
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.]"
|
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}]})
|
result.append({"type": "message", "role": "user", "content": [{"type": "input_text", "text": summary_text}]})
|
||||||
|
|
||||||
for _, call_item in paired_calls:
|
# CRITICAL FIX: Interleave function_calls with their corresponding function_call_outputs
|
||||||
result.append(call_item)
|
# 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:
|
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:
|
for cs_item in compaction_summaries:
|
||||||
result.append(cs_item)
|
result.append(cs_item)
|
||||||
@@ -5569,17 +5589,21 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
# Skip duplicate user text messages, but NEVER skip function responses
|
# 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:
|
if role == "user" and text_key and text_key == last_user_text and not has_function_response:
|
||||||
continue
|
continue
|
||||||
# Only merge same-role messages if they don't contain function calls/responses
|
# CRITICAL FIX: Function calls (model role) and function responses (user role) MUST NOT be merged
|
||||||
# Function calls and responses must remain as separate turns
|
# 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:
|
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)
|
sanitized[-1].setdefault("parts", []).extend(parts)
|
||||||
else:
|
else:
|
||||||
sanitized.append({"role": role, "parts": parts})
|
sanitized.append({"role": role, "parts": parts})
|
||||||
if role == "user" and text_key:
|
if role == "user" and text_key:
|
||||||
last_user_text = text_key
|
last_user_text = text_key
|
||||||
last_role = role
|
last_role = role
|
||||||
|
# Trim leading non-user messages (Google expects conversation to start with user)
|
||||||
while sanitized and sanitized[0].get("role") != "user":
|
while sanitized and sanitized[0].get("role") != "user":
|
||||||
sanitized.pop(0)
|
sanitized.pop(0)
|
||||||
|
# Trim trailing non-user messages (must end with user turn for continuation)
|
||||||
while sanitized and sanitized[-1].get("role") != "user":
|
while sanitized and sanitized[-1].get("role") != "user":
|
||||||
sanitized.pop()
|
sanitized.pop()
|
||||||
contents = sanitized
|
contents = sanitized
|
||||||
|
|||||||
Reference in New Issue
Block a user