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.
@@ -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,8 @@
1
+ """
2
+ Entry point for execution via `python -m antigravity_export`.
3
+ """
4
+
5
+ from antigravity_export.cli import app
6
+
7
+ if __name__ == "__main__":
8
+ app()
@@ -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", []))