antigravity-export 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- antigravity_export/__init__.py +24 -0
- antigravity_export/__main__.py +8 -0
- antigravity_export/api_client.py +134 -0
- antigravity_export/cli.py +635 -0
- antigravity_export/config.py +67 -0
- antigravity_export/connection.py +279 -0
- antigravity_export/exporters/__init__.py +16 -0
- antigravity_export/exporters/file_changes_exporter.py +151 -0
- antigravity_export/exporters/html_exporter.py +908 -0
- antigravity_export/exporters/json_exporter.py +42 -0
- antigravity_export/exporters/markdown_exporter.py +145 -0
- antigravity_export/models.py +177 -0
- antigravity_export/parser.py +402 -0
- antigravity_export/recovery.py +185 -0
- antigravity_export/storage.py +172 -0
- antigravity_export-1.0.0.dist-info/METADATA +324 -0
- antigravity_export-1.0.0.dist-info/RECORD +21 -0
- antigravity_export-1.0.0.dist-info/WHEEL +5 -0
- antigravity_export-1.0.0.dist-info/entry_points.txt +3 -0
- antigravity_export-1.0.0.dist-info/licenses/LICENSE +21 -0
- antigravity_export-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Antigravity Export - CLI & Python library for exporting and recovering conversations
|
|
3
|
+
from the running Google Antigravity Language Server and storage.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = "1.0.0"
|
|
7
|
+
__author__ = "Kayvan Jahanbakhsh <jahanbakhsh@live.com>"
|
|
8
|
+
|
|
9
|
+
from antigravity_export.models import Conversation, Message, ToolCall, FileChange
|
|
10
|
+
from antigravity_export.connection import LanguageServerManager, ConnectionStatus
|
|
11
|
+
from antigravity_export.api_client import LanguageServerClient
|
|
12
|
+
from antigravity_export.recovery import ConversationRecovery
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"__version__",
|
|
16
|
+
"Conversation",
|
|
17
|
+
"Message",
|
|
18
|
+
"ToolCall",
|
|
19
|
+
"FileChange",
|
|
20
|
+
"LanguageServerManager",
|
|
21
|
+
"ConnectionStatus",
|
|
22
|
+
"LanguageServerClient",
|
|
23
|
+
"ConversationRecovery",
|
|
24
|
+
]
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LanguageServer gRPC-Web / HTTP Connect protocol client.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import concurrent.futures
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
import urllib3
|
|
12
|
+
|
|
13
|
+
from antigravity_export.config import AntigravityConfig
|
|
14
|
+
|
|
15
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LanguageServerClient:
|
|
19
|
+
"""Client for executing calls against the Antigravity Language Server."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
port: Optional[int] = None,
|
|
24
|
+
csrf_token: Optional[str] = None,
|
|
25
|
+
config: Optional[AntigravityConfig] = None,
|
|
26
|
+
):
|
|
27
|
+
self.port = port
|
|
28
|
+
self.csrf_token = csrf_token
|
|
29
|
+
self.config = config or AntigravityConfig()
|
|
30
|
+
|
|
31
|
+
def call_api(
|
|
32
|
+
self,
|
|
33
|
+
method: str,
|
|
34
|
+
params: Optional[dict[str, Any]] = None,
|
|
35
|
+
port: Optional[int] = None,
|
|
36
|
+
csrf_token: Optional[str] = None,
|
|
37
|
+
timeout: float = 20.0,
|
|
38
|
+
) -> Optional[dict[str, Any]]:
|
|
39
|
+
"""Call a LanguageServer gRPC-Web Connect API method."""
|
|
40
|
+
p = port or self.port
|
|
41
|
+
token = csrf_token or self.csrf_token
|
|
42
|
+
|
|
43
|
+
if not p or not token:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
url = f"https://localhost:{p}/{self.config.BASE_SERVICE_PATH}/{method}"
|
|
47
|
+
headers = {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
"Connect-Protocol-Version": self.config.CONNECT_PROTOCOL_VERSION,
|
|
50
|
+
self.config.CSRF_HEADER_NAME: token,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
resp = requests.post(
|
|
55
|
+
url,
|
|
56
|
+
headers=headers,
|
|
57
|
+
json=params or {},
|
|
58
|
+
verify=False,
|
|
59
|
+
timeout=timeout,
|
|
60
|
+
)
|
|
61
|
+
if resp.status_code == 200:
|
|
62
|
+
return resp.json()
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
def get_all_trajectories(
|
|
68
|
+
self,
|
|
69
|
+
port: Optional[int] = None,
|
|
70
|
+
csrf_token: Optional[str] = None,
|
|
71
|
+
) -> dict[str, Any]:
|
|
72
|
+
"""Fetch all conversation summaries indexed by a single LS instance."""
|
|
73
|
+
res = self.call_api("GetAllCascadeTrajectories", {}, port=port, csrf_token=csrf_token, timeout=5.0)
|
|
74
|
+
if not res:
|
|
75
|
+
return {}
|
|
76
|
+
return res.get("trajectorySummaries", {})
|
|
77
|
+
|
|
78
|
+
def get_all_trajectories_merged(
|
|
79
|
+
self,
|
|
80
|
+
endpoints: list[dict[str, Any]],
|
|
81
|
+
) -> tuple[dict[str, Any], dict[str, dict[str, Any]], list[tuple[int, str]]]:
|
|
82
|
+
"""Query multiple LS endpoints concurrently and merge trajectories.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
(merged_summaries, cascade_to_endpoint, failed_endpoints)
|
|
86
|
+
"""
|
|
87
|
+
merged: dict[str, Any] = {}
|
|
88
|
+
cascade_ep: dict[str, dict[str, Any]] = {}
|
|
89
|
+
failed_eps: list[tuple[int, str]] = []
|
|
90
|
+
|
|
91
|
+
if not endpoints:
|
|
92
|
+
return merged, cascade_ep, failed_eps
|
|
93
|
+
|
|
94
|
+
def fetch(ep: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
95
|
+
return ep, self.get_all_trajectories(port=ep["port"], csrf_token=ep["csrf_token"])
|
|
96
|
+
|
|
97
|
+
max_workers = min(10, max(1, len(endpoints)))
|
|
98
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
99
|
+
future_to_ep = {executor.submit(fetch, ep): ep for ep in endpoints}
|
|
100
|
+
for future in concurrent.futures.as_completed(future_to_ep):
|
|
101
|
+
ep = future_to_ep[future]
|
|
102
|
+
try:
|
|
103
|
+
_, summaries = future.result()
|
|
104
|
+
if not summaries:
|
|
105
|
+
failed_eps.append((ep["port"], "Empty trajectory response or timeout"))
|
|
106
|
+
else:
|
|
107
|
+
for cid, info in summaries.items():
|
|
108
|
+
if cid not in merged:
|
|
109
|
+
merged[cid] = info
|
|
110
|
+
cascade_ep[cid] = ep
|
|
111
|
+
except Exception as e:
|
|
112
|
+
failed_eps.append((ep["port"], str(e)))
|
|
113
|
+
|
|
114
|
+
return merged, cascade_ep, failed_eps
|
|
115
|
+
|
|
116
|
+
def get_trajectory_steps(
|
|
117
|
+
self,
|
|
118
|
+
cascade_id: str,
|
|
119
|
+
step_count: int = 1000,
|
|
120
|
+
port: Optional[int] = None,
|
|
121
|
+
csrf_token: Optional[str] = None,
|
|
122
|
+
timeout: float = 30.0,
|
|
123
|
+
) -> list[dict[str, Any]]:
|
|
124
|
+
"""Fetch all trajectory steps for a conversation ID (supports on-demand activation)."""
|
|
125
|
+
res = self.call_api(
|
|
126
|
+
"GetCascadeTrajectorySteps",
|
|
127
|
+
{"cascadeId": cascade_id, "startIndex": 0, "endIndex": step_count + 20},
|
|
128
|
+
port=port,
|
|
129
|
+
csrf_token=csrf_token,
|
|
130
|
+
timeout=timeout,
|
|
131
|
+
)
|
|
132
|
+
if not res:
|
|
133
|
+
return []
|
|
134
|
+
return res.get("steps", res.get("messages", []))
|