v3.9.3: sync src/

This commit is contained in:
2026-05-24 19:59:22 +00:00
Unverified
parent 1bd3c9e1ff
commit 87696d14b1

View File

@@ -603,6 +603,63 @@ def _refresh_google_token(token_data, token_path):
print(f"[oauth] refresh failed: {e}", file=sys.stderr) print(f"[oauth] refresh failed: {e}", file=sys.stderr)
return token_data.get("access_token", "") return token_data.get("access_token", "")
# ═══════════════════════════════════════════════════════════════════
# Gemini 3 thought signature preservation
# ═══════════════════════════════════════════════════════════════════
_gemini_sig_store = {}
_gemini_sig_lock = threading.Lock()
def _gemini_store_sig(key, signature):
if not key or not signature:
return
with _gemini_sig_lock:
_gemini_sig_store[key] = {"sig": signature, "ts": time.time()}
def _gemini_get_sig(key):
with _gemini_sig_lock:
item = _gemini_sig_store.get(key)
return item["sig"] if item else None
def _extract_gemini_sig(part):
if not isinstance(part, dict):
return None
return part.get("thoughtSignature") or part.get("thought_signature") or part.get("signature")
def _gemini_reattach_sigs(contents):
for content in contents:
for part in content.get("parts", []):
if not isinstance(part, dict):
continue
if "thoughtSignature" in part:
continue
if "functionCall" in part:
fc = part["functionCall"]
cid = fc.get("id") or fc.get("name")
if cid:
sig = _gemini_get_sig(f"fc:{cid}")
if sig:
part["thoughtSignature"] = sig
if "text" in part and content.get("role") == "model":
turn_key = content.get("_proxy_turn_key")
if turn_key:
sig = _gemini_get_sig(f"turn:{turn_key}")
if sig:
part["thoughtSignature"] = sig
return contents
# Gemini follow-through guardrail
_GEMINI_AGENT_GUARDRAIL = (
"You are running inside Codex as an autonomous coding agent. "
"When the user asks for a change to existing files, do not merely describe the previous work or summarize. "
"You must inspect the existing files, apply edits with tools, and verify the result. "
"If a file path is known from prior context, reuse it. "
"If unsure, list files first. "
"After tool results, continue until the requested change is actually implemented. "
"Never answer only with a plan such as 'I will start by...' or 'I am going to...'. "
"Always emit the actual tool call in the same response."
)
_LOG_FILE = None _LOG_FILE = None
_LOG_FILE_LOCK = threading.Lock() _LOG_FILE_LOCK = threading.Lock()
@@ -4180,44 +4237,47 @@ class Handler(http.server.BaseHTTPRequestHandler):
def _handle_gemini_oauth(self, body, model, stream, tracker=None): def _handle_gemini_oauth(self, body, model, stream, tracker=None):
input_data = body.get("input", "") input_data = body.get("input", "")
policy = provider_policy() policy = provider_policy()
original_model = model
_GEMINI_KEEP_RECENT = 6
_GEMINI_OLD_LIMIT = 3000
_GEMINI_RECENT_LIMIT = 20000
if isinstance(input_data, list) and len(input_data) > 8: if isinstance(input_data, list) and len(input_data) > 8:
n_tool_outputs = sum(1 for it in input_data if isinstance(it, dict) and it.get("type") == "function_call_output") n_tool_outputs = sum(1 for it in input_data if isinstance(it, dict) and it.get("type") == "function_call_output")
if n_tool_outputs > 2: if n_tool_outputs > 2:
tool_indexes = [i for i, it in enumerate(input_data) if isinstance(it, dict) and it.get("type") == "function_call_output"]
recent_set = set(tool_indexes[-_GEMINI_KEEP_RECENT:])
compacted_data = [] compacted_data = []
last_fc_idx = None
for i in range(len(input_data) - 1, -1, -1):
if isinstance(input_data[i], dict) and input_data[i].get("type") == "function_call":
last_fc_idx = i
break
keep_from = last_fc_idx if last_fc_idx is not None else len(input_data)
for i, item in enumerate(input_data): for i, item in enumerate(input_data):
if isinstance(item, dict) and item.get("type") == "function_call_output": if isinstance(item, dict) and item.get("type") == "function_call_output":
o = item.get("output", "") o = item.get("output", "")
if i < keep_from and len(o) > 1500: limit = _GEMINI_RECENT_LIMIT if i in recent_set else _GEMINI_OLD_LIMIT
if len(o) > limit:
item = dict(item) item = dict(item)
summary = o[:600] + f"\n... [compacted {len(o) - 600} chars - earlier tool result]" item["output"] = o[:limit] + f"\n... [proxy compacted: kept {limit} of {len(o)} chars]"
item["output"] = summary
compacted_data.append(item) compacted_data.append(item)
input_data = compacted_data input_data = compacted_data
body = dict(body) body = dict(body)
body["input"] = input_data body["input"] = input_data
print(f"[gemini-compact] {n_tool_outputs} tool outputs, compacted earlier ones to 600 chars", file=sys.stderr) print(f"[gemini-compact] {n_tool_outputs} tool outputs, recent={_GEMINI_RECENT_LIMIT} old={_GEMINI_OLD_LIMIT}", file=sys.stderr)
if OAUTH_PROVIDER == "google-antigravity": if OAUTH_PROVIDER == "google-antigravity":
alias_map = { alias_map = {
"antigravity-gemini-3-flash": "gemini-3-flash", "antigravity-gemini-3-flash": "gemini-3-flash",
"antigravity-gemini-3-pro": "gemini-3-pro-low", "antigravity-gemini-3-pro": "gemini-3-pro",
"antigravity-gemini-3.1-pro": "gemini-3.1-pro-low", "antigravity-gemini-3.1-pro": "gemini-3.1-pro",
"gemini-3-flash-preview": "gemini-3-flash", "gemini-3-flash-preview": "gemini-3-flash-preview",
"gemini-3-pro-preview": "gemini-3-pro-low", "gemini-3-pro-preview": "gemini-3-pro-preview",
"gemini-3.1-pro-preview": "gemini-3.1-pro-low", "gemini-3.1-pro-preview": "gemini-3.1-pro-preview",
"gemini-3-pro": "gemini-3-pro-low", "gemini-3-pro": "gemini-3-pro",
"gemini-3.1-pro": "gemini-3.1-pro-low", "gemini-3.1-pro": "gemini-3.1-pro",
"antigravity-claude-sonnet-4-6": "claude-sonnet-4-6", "antigravity-claude-sonnet-4-6": "claude-sonnet-4-6",
"antigravity-claude-opus-4-6-thinking": "claude-opus-4-6-thinking", "antigravity-claude-opus-4-6-thinking": "claude-opus-4-6-thinking",
} }
model = alias_map.get(model, model) model = alias_map.get(model, model)
if model != original_model:
print(f"[antigravity] model mapped user={original_model} upstream={model}", file=sys.stderr)
pair_errors = validate_tool_pairs(input_data) pair_errors = validate_tool_pairs(input_data)
if pair_errors: if pair_errors:
@@ -4285,7 +4345,11 @@ class Handler(http.server.BaseHTTPRequestHandler):
args = json.loads(args) args = json.loads(args)
except Exception: except Exception:
args = {} args = {}
contents.append({"role": "model", "parts": [{"functionCall": {"name": fname, "args": args, "id": call_id}, "thoughtSignature": "skip_thought_signature_validator"}]}) fc_part = {"functionCall": {"name": fname, "args": args, "id": call_id}}
stored_sig = _gemini_get_sig(f"fc:{call_id}")
if stored_sig:
fc_part["thoughtSignature"] = stored_sig
contents.append({"role": "model", "parts": [fc_part]})
elif t == "function_call_output": elif t == "function_call_output":
call_id = item.get("call_id", item.get("id", "")) call_id = item.get("call_id", item.get("id", ""))
output = item.get("output", "") output = item.get("output", "")
@@ -4367,6 +4431,30 @@ class Handler(http.server.BaseHTTPRequestHandler):
if func_decls: if func_decls:
gemini_tools = [{"functionDeclarations": func_decls}] gemini_tools = [{"functionDeclarations": func_decls}]
if OAUTH_PROVIDER == "google-antigravity":
contents = _gemini_reattach_sigs(contents)
if OAUTH_PROVIDER == "google-antigravity":
guardrail_found = any("autonomous coding agent" in json.dumps(c.get("parts", []), ensure_ascii=False) for c in contents[:2])
if not guardrail_found:
contents.insert(0, {"role": "user", "parts": [{"text": _GEMINI_AGENT_GUARDRAIL}]})
if OAUTH_PROVIDER == "google-antigravity" and isinstance(input_data, list):
latest_user = ""
for item in reversed(input_data):
if item.get("type") == "message" and item.get("role") == "user":
c = item.get("content", "")
if isinstance(c, str):
latest_user = c
elif isinstance(c, list):
latest_user = "\n".join(p.get("text", p.get("input_text", "")) for p in c if isinstance(p, dict))
break
if latest_user:
serialized = json.dumps(contents, ensure_ascii=False)
needle = latest_user.strip()[:120]
if needle and needle not in serialized:
print(f"[antigravity] WARNING: latest user instruction missing from contents! needle={needle[:60]}...", file=sys.stderr)
request_body = {"contents": contents} request_body = {"contents": contents}
if system_parts: if system_parts:
request_body["systemInstruction"] = {"parts": system_parts} request_body["systemInstruction"] = {"parts": system_parts}
@@ -4505,6 +4593,13 @@ class Handler(http.server.BaseHTTPRequestHandler):
print(f"[{self._session_id}] finish without parts: {candidates[0].get('finishReason')}", file=sys.stderr) print(f"[{self._session_id}] finish without parts: {candidates[0].get('finishReason')}", file=sys.stderr)
parts = candidates[0].get("content", {}).get("parts", []) parts = candidates[0].get("content", {}).get("parts", [])
for part in parts: for part in parts:
sig = _extract_gemini_sig(part)
if sig:
if part.get("functionCall"):
fc_id = part["functionCall"].get("id") or part["functionCall"].get("name")
if fc_id:
_gemini_store_sig(f"fc:{fc_id}", sig)
_gemini_store_sig(f"turn:{resp_id}", sig)
if part.get("thought"): if part.get("thought"):
continue continue
if "text" in part and not part.get("functionCall"): if "text" in part and not part.get("functionCall"):
@@ -4530,6 +4625,12 @@ class Handler(http.server.BaseHTTPRequestHandler):
output_items.append({"tool": True}) output_items.append({"tool": True})
last_finish = candidates[0].get("finishReason", "") last_finish = candidates[0].get("finishReason", "")
if last_finish: if last_finish:
part_kinds = []
for p in parts:
if "text" in p: part_kinds.append("text")
if "functionCall" in p: part_kinds.append("functionCall")
if _extract_gemini_sig(p): part_kinds.append("thoughtSignature")
print(f"[{self._session_id}] [antigravity] finish={last_finish} parts={part_kinds} tool_calls={len(current_tool_calls)}", file=sys.stderr)
if OAUTH_PROVIDER == "google-antigravity" and last_finish == "MAX_TOKENS" and full_text and not current_tool_calls: if OAUTH_PROVIDER == "google-antigravity" and last_finish == "MAX_TOKENS" and full_text and not current_tool_calls:
print(f"[{self._session_id}] MAX_TOKENS hit ({len(full_text)} chars), auto-continuing...", file=sys.stderr) print(f"[{self._session_id}] MAX_TOKENS hit ({len(full_text)} chars), auto-continuing...", file=sys.stderr)
break break