v3.10.9: Antigravity context normalizer, Claude thinking fix, endpoint lockdown, cobra91 PR #4

Antigravity-only changes (no other providers touched):
- Production-only endpoints (cloudcode-pa.googleapis.com), sandbox blocked
- AntigravityContextNormalizer: bounded context for every request
- Simple message detector: 'hi' sends minimal payload
- Auto-reset polluted context (200+ items with simple msg)
- Claude thinking: maxOutputTokens=64000, snake_case config, VALIDATED toolConfig
- Claude budgets: low=8192, medium=16384, high=32768

z.ai/OpenRouter (cobra91 PR #4):
- Full OpenClaw attribution headers for z.ai
- X-OpenRouter-Cache header for OpenRouter

Other fixes:
- Linux Re-OAuth: load_oauth_secrets() undefined, now inline
- GLib.idle_add lambda truthy tuple fix
- Project discovery uses production endpoint
This commit is contained in:
Roman | RyzenAdvanced
2026-05-25 22:28:43 +04:00
Unverified
parent a904aeadb0
commit a1d0fc3707
8 changed files with 508 additions and 40 deletions

View File

@@ -1300,7 +1300,6 @@ def forwarded_headers(request_headers, extra=None, browser_ua=False):
headers.update(extra)
return headers
def _openrouter_extra():
if not TARGET_URL:
return {}
@@ -4258,6 +4257,177 @@ def _auto_continue_gemini(handler, flush_event, message_id, model, gen_config, g
break
return accumulated_text
_ANTIGRAVITY_MAX_CONTENTS = 20
_ANTIGRAVITY_MAX_TOOL_VERBATIM = 2
_ANTIGRAVITY_MAX_TOOL_CHARS = 2000
_ANTIGRAVITY_MAX_OLD_SUMMARY_CHARS = 1200
_ANTIGRAVITY_SOFT_CHARS = 120000
_ANTIGRAVITY_HARD_CHARS = 250000
_ANTIGRAVITY_EMERGENCY_CHARS = 500000
_ANTIGRAVITY_SIMPLE_WORDS = frozenset({"hi", "hello", "hey", "test", "ping", "thanks", "thank you", "ok", "okay", "yes", "no", "cool", "nice", "good", "great", "done", "go", "stop", "yep", "nope", "sure", "right", "correct", "continue", "cont", "k", "thx", "ty", "np", "lol", "brb", "bye"})
_ANTIGRAVITY_EDIT_WORDS = frozenset(("change", "fix", "update", "redesign", "rewrite", "modify", "improve", "replace", "edit", "make it", "add", "remove", "delete", "rename", "move", "convert", "create", "build", "implement"))
_ANTIGRAVITY_REFERENCE_WORDS = frozenset(("previous", "file", "error", "again", "that", "this", "it", "same", "last", "above", "earlier", "before", "earlier output", "last error", "previous result", "what was", "show me", "give me"))
def _antigravity_is_simple_user(text):
if not text:
return True
stripped = text.strip().lower()
if stripped in _ANTIGRAVITY_SIMPLE_WORDS:
return True
if len(stripped) < 30:
words = set(stripped.split())
if not words.intersection(_ANTIGRAVITY_REFERENCE_WORDS) and not words.intersection(_ANTIGRAVITY_EDIT_WORDS):
return True
return False
def _antigravity_normalize_context(input_data):
if not isinstance(input_data, list) or len(input_data) < 2:
return input_data
latest_user = ""
latest_user_idx = -1
for i in range(len(input_data) - 1, -1, -1):
item = input_data[i]
if isinstance(item, dict) and 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))
latest_user_idx = i
break
if not latest_user:
return input_data
is_simple = _antigravity_is_simple_user(latest_user)
n_raw = len(input_data)
n_tool_outputs = sum(1 for it in input_data if isinstance(it, dict) and it.get("type") == "function_call_output")
n_tool_calls = sum(1 for it in input_data if isinstance(it, dict) and it.get("type") == "function_call")
auto_reset = (n_raw > 200 or n_tool_outputs > 20) and is_simple
if os.environ.get("ANTIGRAVITY_AUTO_RESET_POLLUTED_CONTEXT", "1") != "1":
auto_reset = False
if is_simple and (auto_reset or n_tool_outputs == 0):
system_items = [it for it in input_data if isinstance(it, dict) and it.get("type") == "message" and it.get("role") in ("developer", "system")]
user_item = input_data[latest_user_idx]
result = system_items + [user_item] if system_items else [user_item]
print(f"[antigravity-context] raw_items={n_raw} compacted_items={n_raw} final_items={len(result)}", file=sys.stderr)
print(f"[antigravity-context] raw_tool_outputs={n_tool_outputs} kept_tool_outputs=0", file=sys.stderr)
print(f"[antigravity-context] simple_latest_user=true auto_reset={auto_reset}", file=sys.stderr)
return result
dev_messages = []
recent_items = []
tool_outputs = []
other_items = []
for i, item in enumerate(input_data):
if not isinstance(item, dict):
continue
t = item.get("type")
if t == "message" and item.get("role") in ("developer", "system"):
dev_messages.append(item)
elif t == "function_call_output":
tool_outputs.append((i, item))
elif t in ("function_call",):
other_items.append((i, item))
elif t == "message":
recent_items.append((i, item))
latest_words = set(latest_user.strip().lower().split())
has_edit_intent = bool(latest_words.intersection(_ANTIGRAVITY_EDIT_WORDS))
has_ref_intent = bool(latest_words.intersection(_ANTIGRAVITY_REFERENCE_WORDS))
keep_tools = 2 if (has_edit_intent or has_ref_intent) else 1
kept_tools = tool_outputs[-keep_tools:] if tool_outputs and (has_edit_intent or has_ref_intent) else []
for idx_t, t_item in enumerate(kept_tools):
orig = t_item[1]
out = orig.get("output", "")
if isinstance(out, str) and len(out) > _ANTIGRAVITY_MAX_TOOL_CHARS:
new_item = dict(orig)
new_item["output"] = out[:_ANTIGRAVITY_MAX_TOOL_CHARS] + f"\n... [truncated: kept {_ANTIGRAVITY_MAX_TOOL_CHARS} of {len(out)} chars]"
kept_tools[idx_t] = (t_item[0], new_item)
n_summarized = len(tool_outputs) - len(kept_tools)
tail_start = max(0, len(recent_items) - 6)
recent_tail = recent_items[tail_start:]
tool_call_ids = set()
for _, t_item in kept_tools:
cid = t_item.get("call_id", t_item.get("id", ""))
if cid:
tool_call_ids.add(cid)
paired_calls = []
for idx, item in other_items:
cid = item.get("call_id", item.get("id", ""))
if cid in tool_call_ids:
paired_calls.append((idx, item))
result = list(dev_messages)
if n_summarized > 0:
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)
for _, tool_item in kept_tools:
result.append(tool_item)
for _, msg_item in recent_tail:
if msg_item is not input_data[latest_user_idx]:
result.append(msg_item)
latest_hash = hashlib.sha256(" ".join(latest_user.strip().split()).encode()).hexdigest()
already_present = False
for r in result:
if isinstance(r, dict) and r.get("type") == "message" and r.get("role") == "user":
c = r.get("content", "")
if isinstance(c, str):
rh = hashlib.sha256(" ".join(c.strip().split()).encode()).hexdigest()
elif isinstance(c, list):
combined = " ".join(p.get("text", p.get("input_text", "")) for p in c if isinstance(p, dict))
rh = hashlib.sha256(" ".join(combined.strip().split()).encode()).hexdigest()
else:
rh = ""
if rh == latest_hash:
already_present = True
break
if not already_present:
result.append(input_data[latest_user_idx])
total_chars = sum(len(json.dumps(it, ensure_ascii=False)) for it in result)
if total_chars > _ANTIGRAVITY_EMERGENCY_CHARS:
print(f"[antigravity-context] EMERGENCY: {total_chars} chars exceeds limit, resetting to minimal", file=sys.stderr)
result = list(dev_messages) + [input_data[latest_user_idx]]
total_chars = sum(len(json.dumps(it, ensure_ascii=False)) for it in result)
while len(result) > _ANTIGRAVITY_MAX_CONTENTS and total_chars > _ANTIGRAVITY_SOFT_CHARS:
for i in range(1, len(result) - 1):
if isinstance(result[i], dict) and result[i].get("type") in ("message", "function_call_output"):
removed = result.pop(i)
total_chars -= len(json.dumps(removed, ensure_ascii=False))
break
else:
break
est_tokens = total_chars // 4
print(f"[antigravity-context] raw_items={n_raw} final_items={len(result)}", file=sys.stderr)
print(f"[antigravity-context] raw_tool_outputs={n_tool_outputs} kept_tool_outputs={len(kept_tools)} summarized_tool_outputs={n_summarized}", file=sys.stderr)
print(f"[antigravity-context] simple_latest_user={is_simple} auto_reset={auto_reset}", file=sys.stderr)
print(f"[antigravity-context] final_chars={total_chars} estimated_tokens={est_tokens}", file=sys.stderr)
return result
class Handler(http.server.BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
@@ -4623,6 +4793,11 @@ class Handler(http.server.BaseHTTPRequestHandler):
body = dict(body)
body["input"] = input_data
if OAUTH_PROVIDER == "google-antigravity" and isinstance(input_data, list):
input_data = _antigravity_normalize_context(input_data)
body = dict(body)
body["input"] = input_data
access_token = _refresh_oauth_token()
token_name = "google-antigravity-oauth-token.json" if OAUTH_PROVIDER == "google-antigravity" else "google-cli-oauth-token.json"
token_path = os.path.join(os.path.expanduser("~"), ".cache", "codex-proxy", token_name)
@@ -4743,7 +4918,26 @@ class Handler(http.server.BaseHTTPRequestHandler):
if body.get("top_p") is not None:
gen_config["topP"] = body["top_p"]
if REASONING_ENABLED and REASONING_EFFORT != "none":
_is_claude_model = "claude" in model.lower()
_is_claude_thinking = _is_claude_model and "thinking" in model.lower()
if OAUTH_PROVIDER == "google-antigravity" and _is_claude_thinking:
if REASONING_ENABLED and REASONING_EFFORT != "none":
budget = {"low": 8192, "medium": 16384, "high": 32768}.get(REASONING_EFFORT, 16384)
else:
budget = 16384
gen_config["thinkingConfig"] = {
"include_thoughts": True,
"thinking_budget": budget,
}
current_max = gen_config.get("maxOutputTokens", 0)
if not current_max or current_max <= budget:
gen_config["maxOutputTokens"] = 64000
print(f"[antigravity-claude] thinking model={model} budget={budget} maxOutputTokens={gen_config.get('maxOutputTokens')}", file=sys.stderr)
elif OAUTH_PROVIDER == "google-antigravity" and _is_claude_model:
if "thinkingConfig" in gen_config:
del gen_config["thinkingConfig"]
elif REASONING_ENABLED and REASONING_EFFORT != "none":
budget = {"low": 2048, "medium": 8192, "high": 24576}.get(REASONING_EFFORT, 8192)
gen_config["thinkingConfig"] = {"includeThoughts": True, "thinkingBudget": budget}
@@ -4823,6 +5017,11 @@ class Handler(http.server.BaseHTTPRequestHandler):
if gemini_tools:
request_body["tools"] = gemini_tools
if OAUTH_PROVIDER == "google-antigravity" and _is_claude_model and gemini_tools:
request_body["toolConfig"] = {"functionCallingConfig": {"mode": "VALIDATED"}}
if _is_claude_thinking:
print(f"[antigravity-claude] applied VALIDATED toolConfig for thinking model", file=sys.stderr)
wrapped = {
"project": project_id,
"model": model,
@@ -4833,13 +5032,17 @@ class Handler(http.server.BaseHTTPRequestHandler):
wrapped["userAgent"] = "antigravity"
wrapped["requestId"] = f"agent-{uuid.uuid4().hex[:12]}"
endpoints = ([
"https://cloudcode-pa.googleapis.com",
"https://daily-cloudcode-pa.sandbox.googleapis.com",
"https://autopush-cloudcode-pa.sandbox.googleapis.com",
] if OAUTH_PROVIDER == "google-antigravity" else [
"https://cloudcode-pa.googleapis.com",
])
_allow_staging = os.environ.get("ALLOW_ANTIGRAVITY_STAGING", "0") == "1"
if OAUTH_PROVIDER == "google-antigravity":
_antigravity_endpoints = ["https://cloudcode-pa.googleapis.com"]
if _allow_staging:
_antigravity_endpoints.extend([
"https://daily-cloudcode-pa.sandbox.googleapis.com",
"https://autopush-cloudcode-pa.sandbox.googleapis.com",
])
endpoints = _antigravity_endpoints
else:
endpoints = ["https://cloudcode-pa.googleapis.com"]
action = "streamGenerateContent" if stream else "generateContent"
url_suffix = f"v1internal:{action}?alt=sse" if stream else f"v1internal:{action}"
@@ -4888,7 +5091,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
if e.code == 403 and "SERVICE_DISABLED" in err_body[:500] and ep != endpoints[-1]:
print(f"[{self._session_id}] {ep} SERVICE_DISABLED, trying next endpoint", file=sys.stderr)
continue
if e.code == 429 and ep != endpoints[-1]:
if e.code == 429 and ep != endpoints[-1] and _allow_staging:
print(f"[{self._session_id}] {ep} HTTP 429, trying next endpoint", file=sys.stderr)
continue
if e.code == 429: