11 Commits

7 changed files with 834 additions and 157 deletions

View File

@@ -1,5 +1,63 @@
# Changelog # Changelog
## v3.11.12 (2026-05-26)
**New Antigravity v2 Handler (Mimicking anti-api)**
### New Features
- **Complete rewrite of Antigravity handler** based on https://github.com/ink1ing/anti-api approach
- Safety settings (all OFF), stopSequences, sessionId, requestType: agent
- functionResponse uses `response: { result: string }` format matching anti-api
- Endpoint priority: `daily-cloudcode-pa.googleapis.com` first
- Simplified sanitizer: only deduplicates consecutive user text, never touches tool messages
## v3.11.11 (2026-05-26)
## v3.11.11 (2026-05-26)
**Antigravity Fix: Stricter function_call/output Pairing + Gemini Sanitizer Rewrite (PR #12)**
### Bug Fixes
- **Stricter function_call/output pairing**: Only includes pairs where BOTH call and output exist — no orphan calls sent to Gemini
- **Gemini sanitizer rewritten**: Tool messages (`functionCall`/`functionResponse`) are always preserved as-is, never merged or skipped
- **Text merging more conservative**: Checks last message for tool content before merging consecutive text messages
- **Final trimming safe**: Only removes plain `message` items, never `function_call_output` (which would break tool pairs)
- **Merge PR #12**: Fix by qwen-chat coder
## v3.11.10 (2026-05-26)
## 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)**
### Bug Fixes
- **Fix Antigravity multi-turn tool use**: The Gemini message sanitizer was incorrectly merging/dropping `functionCall` and `functionResponse` turns, causing Antigravity to think forever without responding. These turns are now always preserved as separate messages.
- **Merge PR #10**: `fix: preserve functionCall/functionResponse in Gemini sanitizer` (qwen-chat coder)
## v3.11.8 (2026-05-26)
## v3.11.8 (2026-05-26)
**Vision Cache Persistence, PR #8 Merge**
### New Features
- **Vision description cache persisted across requests**: Image descriptions from the vision fallback API are now cached in a file (`~/.cache/codex-proxy/vision-cache.json`) so the same image URL is never described twice — saves API calls and latency
- **Merge PR #8**: `fix: persist vision description cache across requests` (cobra91)
## v3.11.7 (2026-05-26) ## v3.11.7 (2026-05-26)
**Vision Auto-Detect, Proactive Non-Vision Model Detection, Unit Tests, Bug Fixes** **Vision Auto-Detect, Proactive Non-Vision Model Detection, Unit Tests, Bug Fixes**

Binary file not shown.

Binary file not shown.

View File

@@ -27,6 +27,22 @@ model_catalog_json = ""
""" """
CHANGELOG = [ CHANGELOG = [
("3.11.12", "2026-05-26", [
"New Antigravity v2 handler mimicking anti-api",
"Safety settings, stopSequences, simplified sanitizer",
]),
("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",
]),
("3.11.8", "2026-05-26", [
"Vision cache persisted across requests (PR #8 merge)",
"No redundant vision API calls for same image URL",
]),
("3.11.7", "2026-05-26", [ ("3.11.7", "2026-05-26", [
"Vision auto-detect: uses provider's vision model for image description", "Vision auto-detect: uses provider's vision model for image description",
"Vision preprocessing replaces image stripping", "Vision preprocessing replaces image stripping",

View File

@@ -83,6 +83,29 @@ model_catalog_json = ""
""" """
CHANGELOG = [ CHANGELOG = [
("3.11.12", "2026-05-26", [
"New Antigravity v2 handler mimicking anti-api approach",
"Safety settings, stopSequences, sessionId, requestType: agent",
"Simplified sanitizer preserving functionCall/functionResponse",
"Endpoint priority: daily-cloudcode-pa first",
"functionResponse uses response.result (string) format",
]),
("3.11.11", "2026-05-26", [
"Final trimming only removes plain messages, never function_call_output",
]),
("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",
]),
("3.11.8", "2026-05-26", [
"Vision description cache persisted across requests (no redundant API calls for same image)",
"Merge PR #8: fix vision cache persistence across requests",
]),
("3.11.7", "2026-05-26", [ ("3.11.7", "2026-05-26", [
"Vision auto-detect: uses provider's own vision model (e.g. 0G-Qwen-VL) as fallback for image description", "Vision auto-detect: uses provider's own vision model (e.g. 0G-Qwen-VL) as fallback for image description",
"Vision preprocessing replaces image stripping: images described via API instead of just removed", "Vision preprocessing replaces image stripping: images described via API instead of just removed",

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@ Uses only stdlib unittest + unittest.mock (zero pip dependencies).
""" """
import json import json
import os
import sys import sys
import time import time
import unittest import unittest
@@ -19,7 +20,7 @@ import importlib
_spec = importlib.util.spec_from_file_location( _spec = importlib.util.spec_from_file_location(
"translate_proxy", "translate_proxy",
r"C:\dev\Codex-Launcher---Any-AI-Porovider\src\translate-proxy.py", os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src", "translate-proxy.py"),
) )
tp = importlib.util.module_from_spec(_spec) tp = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(tp) _spec.loader.exec_module(tp)
@@ -121,36 +122,33 @@ class TestExtractXmlToolCalls(unittest.TestCase):
self.assertEqual(tp._extract_xml_tool_calls("just plain text"), []) self.assertEqual(tp._extract_xml_tool_calls("just plain text"), [])
def test_single_tool_call(self): def test_single_tool_call(self):
# Regex: <tool_call>(\w+)(.*?)</tool_call> text = '<invoke><exec_command>echo hi</exec_command></invoke>'
# Format: <tool_call>NAME>CONTENT</tool_call>
text = '<tool_call>bash>echo hi</tool_call>'
results = tp._extract_xml_tool_calls(text) results = tp._extract_xml_tool_calls(text)
self.assertEqual(len(results), 1) self.assertEqual(len(results), 1)
self.assertEqual(results[0]["name"], "bash") self.assertEqual(results[0]["name"], "exec_command")
self.assertIn("call_id", results[0]) self.assertIn("call_id", results[0])
self.assertTrue(results[0]["call_id"].startswith("xml_")) self.assertTrue(results[0]["call_id"].startswith("xml_"))
def test_multiple_tool_calls(self): def test_multiple_tool_calls(self):
text = ( text = (
'<tool_call>bash>echo hi</tool_call>' '<invoke><exec_command>echo hi</exec_command></invoke>'
'<tool_call>edit>test.py</tool_call>' '<invoke><exec_command>test.py</exec_command></invoke>'
) )
results = tp._extract_xml_tool_calls(text) results = tp._extract_xml_tool_calls(text)
self.assertEqual(len(results), 2) self.assertEqual(len(results), 2)
self.assertEqual(results[0]["name"], "bash") self.assertEqual(results[0]["name"], "exec_command")
self.assertEqual(results[1]["name"], "edit") self.assertEqual(results[1]["name"], "exec_command")
def test_json_args(self): def test_json_args(self):
text = '<tool_call>tool>{"key": "value"}</tool_call>' text = '<invoke><exec_command>{"key": "value"}</exec_command></invoke>'
results = tp._extract_xml_tool_calls(text) results = tp._extract_xml_tool_calls(text)
self.assertEqual(len(results), 1) self.assertEqual(len(results), 1)
self.assertEqual(results[0]["name"], "tool") self.assertEqual(results[0]["name"], "exec_command")
args = json.loads(results[0]["args"]) args = json.loads(results[0]["args"])
# JSON parsing of XML content may vary - just check result exists
self.assertIn("args", results[0]) self.assertIn("args", results[0])
def test_code_fenced_args(self): def test_code_fenced_args(self):
text = '<tool_call>tool>{"a": 1}</tool_call>' text = '<invoke><exec_command>{"a": 1}</exec_command></invoke>'
results = tp._extract_xml_tool_calls(text) results = tp._extract_xml_tool_calls(text)
self.assertEqual(len(results), 1) self.assertEqual(len(results), 1)