Yash030 commited on
Commit
c8258aa
·
1 Parent(s): 2217e00

feat: implement memory_antigravity_sync natively in Python

Browse files
Files changed (1) hide show
  1. src/app.py +211 -0
src/app.py CHANGED
@@ -482,6 +482,21 @@ def api_session_start():
482
  except Exception as e:
483
  return jsonify({"error": str(e)}), 400
484
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  @app.route("/agentmemory/session/end", methods=["POST"])
486
  def api_session_end():
487
  auth_err = check_auth()
@@ -1551,6 +1566,182 @@ def enrich_search_results(kv_inst, results):
1551
  enriched.append(item)
1552
  return enriched
1553
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1554
  @app.route("/agentmemory/mcp/tools", methods=["GET"])
1555
  def mcp_tools_list():
1556
  auth_err = check_auth()
@@ -1794,6 +1985,19 @@ def mcp_tools_list():
1794
  },
1795
  "required": ["content", "project"]
1796
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
1797
  }
1798
  ]
1799
  return jsonify({"tools": tools}), 200
@@ -1973,6 +2177,13 @@ def mcp_tools_call():
1973
  res = functions.remember(kv, payload)
1974
  text_out = json.dumps(res)
1975
 
 
 
 
 
 
 
 
1976
  else:
1977
  return jsonify({"error": f"unknown tool: {name}"}), 400
1978
 
 
482
  except Exception as e:
483
  return jsonify({"error": str(e)}), 400
484
 
485
+ @app.route("/agentmemory/antigravity/sync", methods=["POST"])
486
+ def api_antigravity_sync():
487
+ auth_err = check_auth()
488
+ if auth_err:
489
+ return auth_err
490
+ try:
491
+ body = request.get_json(force=True) or {}
492
+ mode = body.get("mode") or "current_session"
493
+ current_convo = body.get("currentConversationId")
494
+ current_folder = body.get("currentFolder")
495
+ res = perform_antigravity_sync(mode, current_convo, current_folder)
496
+ return jsonify(res), 200
497
+ except Exception as e:
498
+ return jsonify({"error": str(e)}), 500
499
+
500
  @app.route("/agentmemory/session/end", methods=["POST"])
501
  def api_session_end():
502
  auth_err = check_auth()
 
1566
  enriched.append(item)
1567
  return enriched
1568
 
1569
+ def perform_antigravity_sync(mode="current_session", current_conversation_id=None, current_folder=None):
1570
+ import os
1571
+ import json
1572
+ import glob
1573
+ import re
1574
+
1575
+ brain_dir = os.path.join(os.path.expanduser("~"), ".gemini", "antigravity", "brain")
1576
+ if not os.path.exists(brain_dir):
1577
+ return {"success": False, "syncedSessions": [], "observationsAdded": 0, "error": f"Brain directory not found at {brain_dir}"}
1578
+
1579
+ pattern = os.path.join(brain_dir, "*", ".system_generated", "logs", "transcript.jsonl")
1580
+ files = glob.glob(pattern)
1581
+ if not files:
1582
+ return {"success": True, "syncedSessions": [], "observationsAdded": 0}
1583
+
1584
+ conversations = []
1585
+ for fpath in files:
1586
+ try:
1587
+ mtime = os.path.getmtime(fpath)
1588
+ convo_id = os.path.basename(os.path.dirname(os.path.dirname(os.path.dirname(fpath))))
1589
+ conversations.append({
1590
+ "id": convo_id,
1591
+ "transcriptPath": fpath,
1592
+ "mtime": mtime
1593
+ })
1594
+ except Exception:
1595
+ pass
1596
+
1597
+ if not conversations:
1598
+ return {"success": True, "syncedSessions": [], "observationsAdded": 0}
1599
+
1600
+ conversations.sort(key=lambda x: x["mtime"], reverse=True)
1601
+
1602
+ targets = []
1603
+ if mode == "current_session":
1604
+ if current_conversation_id:
1605
+ match = next((c for c in conversations if c["id"] == current_conversation_id), None)
1606
+ if match:
1607
+ targets = [match]
1608
+ else:
1609
+ targets = [conversations[0]]
1610
+ elif mode == "current_folder":
1611
+ target_folder = current_folder if current_folder else ""
1612
+ if not target_folder:
1613
+ target_folder = os.getcwd()
1614
+
1615
+ target_folder_norm = target_folder.replace("\\", "/").lower().strip()
1616
+
1617
+ for convo in conversations:
1618
+ try:
1619
+ with open(convo["transcriptPath"], "r", encoding="utf-8") as tf:
1620
+ text = tf.read().lower()
1621
+ text_norm = text.replace("\\/", "/").replace("\\\\", "/")
1622
+ if target_folder_norm in text_norm:
1623
+ targets.append(convo)
1624
+ except Exception:
1625
+ pass
1626
+ elif mode == "all":
1627
+ targets = conversations
1628
+ else:
1629
+ return {"success": False, "syncedSessions": [], "observationsAdded": 0, "error": f"Invalid mode: {mode}"}
1630
+
1631
+ if not targets:
1632
+ return {"success": True, "syncedSessions": [], "observationsAdded": 0}
1633
+
1634
+ synced_sessions = []
1635
+ observations_added = 0
1636
+
1637
+ for convo in targets:
1638
+ convo_id = convo["id"]
1639
+ tpath = convo["transcriptPath"]
1640
+ session_id = f"antigravity_{convo_id[:18].replace('-', '_')}"
1641
+
1642
+ project_path = None
1643
+ try:
1644
+ with open(tpath, "r", encoding="utf-8") as tf:
1645
+ first_line = tf.readline()
1646
+ if first_line:
1647
+ step = json.loads(first_line)
1648
+ match = re.search(r"\[([^\]]+)\]\s*->\s*\[([^\]]+)\]", step.get("content", ""))
1649
+ if match:
1650
+ project_path = match.group(2)
1651
+ except Exception:
1652
+ pass
1653
+
1654
+ if not project_path:
1655
+ project_path = os.getcwd()
1656
+
1657
+ turns = []
1658
+ current_prompt = None
1659
+ current_timestamp = None
1660
+
1661
+ try:
1662
+ with open(tpath, "r", encoding="utf-8") as tf:
1663
+ for line in tf:
1664
+ if not line.strip():
1665
+ continue
1666
+ try:
1667
+ step = json.loads(line)
1668
+ step_type = step.get("type")
1669
+ if step_type == "USER_INPUT":
1670
+ p_text = step.get("content", "")
1671
+ if "<USER_REQUEST>" in p_text:
1672
+ parts = p_text.split("<USER_REQUEST>")
1673
+ if len(parts) > 1:
1674
+ p_text = parts[1]
1675
+ if "</USER_REQUEST>" in p_text:
1676
+ p_text = p_text.split("</USER_REQUEST>")[0]
1677
+ current_prompt = p_text.strip()
1678
+ current_timestamp = step.get("created_at")
1679
+ elif step_type == "PLANNER_RESPONSE" and current_prompt:
1680
+ turns.append({
1681
+ "prompt": current_prompt,
1682
+ "response": step.get("content", ""),
1683
+ "timestamp": current_timestamp or step.get("created_at") or datetime_now_iso()
1684
+ })
1685
+ current_prompt = None
1686
+ current_timestamp = None
1687
+ except Exception:
1688
+ pass
1689
+ except Exception:
1690
+ continue
1691
+
1692
+ if not turns:
1693
+ continue
1694
+
1695
+ existing_inputs = set()
1696
+ obs_list = kv.list(KV.observations(session_id))
1697
+ for obs in obs_list:
1698
+ tool_input = obs.get("toolInput") or (obs.get("raw") or {}).get("tool_input")
1699
+ if tool_input:
1700
+ existing_inputs.add(tool_input.strip())
1701
+
1702
+ session_exists = kv.get(KV.sessions, session_id) is not None
1703
+ if not session_exists:
1704
+ session = {
1705
+ "id": session_id,
1706
+ "project": project_path,
1707
+ "cwd": project_path,
1708
+ "startedAt": datetime_now_iso(),
1709
+ "status": "active",
1710
+ "observationCount": 0,
1711
+ "summary": f"Antigravity Pair Programming ({convo_id[:8]})",
1712
+ "firstPrompt": f"Antigravity Pair Programming ({convo_id[:8]})",
1713
+ "agentId": "antigravity"
1714
+ }
1715
+ functions.create_session(kv, session)
1716
+
1717
+ convo_synced = False
1718
+ for turn in turns:
1719
+ prompt = turn["prompt"]
1720
+ if prompt.strip() in existing_inputs:
1721
+ continue
1722
+
1723
+ payload = {
1724
+ "sessionId": session_id,
1725
+ "project": project_path,
1726
+ "cwd": project_path,
1727
+ "hookType": "post_tool_use",
1728
+ "timestamp": turn["timestamp"],
1729
+ "agentId": "antigravity",
1730
+ "data": {
1731
+ "tool_name": "conversation",
1732
+ "tool_input": prompt,
1733
+ "tool_output": turn["response"],
1734
+ }
1735
+ }
1736
+ functions.observe(kv, payload)
1737
+ observations_added += 1
1738
+ convo_synced = True
1739
+
1740
+ if convo_synced:
1741
+ synced_sessions.append(convo_id)
1742
+
1743
+ return {"success": True, "syncedSessions": synced_sessions, "observationsAdded": observations_added}
1744
+
1745
  @app.route("/agentmemory/mcp/tools", methods=["GET"])
1746
  def mcp_tools_list():
1747
  auth_err = check_auth()
 
1985
  },
1986
  "required": ["content", "project"]
1987
  }
1988
+ },
1989
+ {
1990
+ "name": "memory_antigravity_sync",
1991
+ "description": "Sync Antigravity chat transcripts to agentmemory. Supports syncing the current session, all sessions, or sessions associated with the current folder.",
1992
+ "inputSchema": {
1993
+ "type": "object",
1994
+ "properties": {
1995
+ "mode": {"type": "string", "description": "Sync mode: current_session (default), current_folder, or all"},
1996
+ "currentConversationId": {"type": "string", "description": "Optional conversation ID of the current active session"},
1997
+ "currentFolder": {"type": "string", "description": "Optional current folder path to filter by"}
1998
+ },
1999
+ "required": ["mode"]
2000
+ }
2001
  }
2002
  ]
2003
  return jsonify({"tools": tools}), 200
 
2177
  res = functions.remember(kv, payload)
2178
  text_out = json.dumps(res)
2179
 
2180
+ elif name == "memory_antigravity_sync":
2181
+ mode = args.get("mode") or "current_session"
2182
+ current_convo = args.get("currentConversationId")
2183
+ current_folder = args.get("currentFolder")
2184
+ res = perform_antigravity_sync(mode, current_convo, current_folder)
2185
+ text_out = json.dumps(res)
2186
+
2187
  else:
2188
  return jsonify({"error": f"unknown tool: {name}"}), 400
2189