v3.9.3: sync src/
This commit is contained in:
@@ -603,6 +603,63 @@ def _refresh_google_token(token_data, token_path):
|
||||
print(f"[oauth] refresh failed: {e}", file=sys.stderr)
|
||||
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_LOCK = threading.Lock()
|
||||
|
||||
@@ -4180,44 +4237,47 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
def _handle_gemini_oauth(self, body, model, stream, tracker=None):
|
||||
input_data = body.get("input", "")
|
||||
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:
|
||||
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:
|
||||
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 = []
|
||||
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):
|
||||
if isinstance(item, dict) and item.get("type") == "function_call_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)
|
||||
summary = o[:600] + f"\n... [compacted {len(o) - 600} chars - earlier tool result]"
|
||||
item["output"] = summary
|
||||
item["output"] = o[:limit] + f"\n... [proxy compacted: kept {limit} of {len(o)} chars]"
|
||||
compacted_data.append(item)
|
||||
input_data = compacted_data
|
||||
body = dict(body)
|
||||
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":
|
||||
alias_map = {
|
||||
"antigravity-gemini-3-flash": "gemini-3-flash",
|
||||
"antigravity-gemini-3-pro": "gemini-3-pro-low",
|
||||
"antigravity-gemini-3.1-pro": "gemini-3.1-pro-low",
|
||||
"gemini-3-flash-preview": "gemini-3-flash",
|
||||
"gemini-3-pro-preview": "gemini-3-pro-low",
|
||||
"gemini-3.1-pro-preview": "gemini-3.1-pro-low",
|
||||
"gemini-3-pro": "gemini-3-pro-low",
|
||||
"gemini-3.1-pro": "gemini-3.1-pro-low",
|
||||
"antigravity-gemini-3-pro": "gemini-3-pro",
|
||||
"antigravity-gemini-3.1-pro": "gemini-3.1-pro",
|
||||
"gemini-3-flash-preview": "gemini-3-flash-preview",
|
||||
"gemini-3-pro-preview": "gemini-3-pro-preview",
|
||||
"gemini-3.1-pro-preview": "gemini-3.1-pro-preview",
|
||||
"gemini-3-pro": "gemini-3-pro",
|
||||
"gemini-3.1-pro": "gemini-3.1-pro",
|
||||
"antigravity-claude-sonnet-4-6": "claude-sonnet-4-6",
|
||||
"antigravity-claude-opus-4-6-thinking": "claude-opus-4-6-thinking",
|
||||
}
|
||||
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)
|
||||
if pair_errors:
|
||||
@@ -4285,7 +4345,11 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
args = json.loads(args)
|
||||
except Exception:
|
||||
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":
|
||||
call_id = item.get("call_id", item.get("id", ""))
|
||||
output = item.get("output", "")
|
||||
@@ -4367,6 +4431,30 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
if 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}
|
||||
if 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)
|
||||
parts = candidates[0].get("content", {}).get("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"):
|
||||
continue
|
||||
if "text" in part and not part.get("functionCall"):
|
||||
@@ -4530,6 +4625,12 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
output_items.append({"tool": True})
|
||||
last_finish = candidates[0].get("finishReason", "")
|
||||
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:
|
||||
print(f"[{self._session_id}] MAX_TOKENS hit ({len(full_text)} chars), auto-continuing...", file=sys.stderr)
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user