Spaces:
Running
Running
Commit
Β·
ab5c7fc
1
Parent(s):
fba3bad
updated g-cal
Browse files- backend/agent.py +44 -23
- backend/api.py +42 -6
backend/agent.py
CHANGED
|
@@ -274,21 +274,26 @@ class Attendee(TypedDict):
|
|
| 274 |
@tool("schedule_meeting")
|
| 275 |
def schedule_meeting(
|
| 276 |
title: str,
|
| 277 |
-
start_rfc3339: str,
|
| 278 |
-
end_rfc3339: str,
|
| 279 |
-
attendees:
|
| 280 |
-
description:
|
| 281 |
-
location:
|
| 282 |
calendar_id: str = "primary",
|
| 283 |
make_meet_link: bool = True,
|
|
|
|
| 284 |
) -> str:
|
| 285 |
"""
|
| 286 |
Create a Google Calendar event (and optional Google Meet link).
|
| 287 |
Returns a human-readable confirmation.
|
| 288 |
"""
|
| 289 |
try:
|
| 290 |
-
tool_log.info(f"schedule_meeting args title={title} start={start_rfc3339} end={end_rfc3339} attendees={[a.get('email') for a in (attendees or [])]}")
|
| 291 |
svc = get_gcal_service()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
|
| 293 |
body = {
|
| 294 |
"summary": title,
|
|
@@ -296,29 +301,45 @@ def schedule_meeting(
|
|
| 296 |
"location": location or "",
|
| 297 |
"start": {"dateTime": start_rfc3339},
|
| 298 |
"end": {"dateTime": end_rfc3339},
|
| 299 |
-
"attendees": [{"email": a["email"], "optional": a.get("optional", False)}
|
|
|
|
| 300 |
}
|
|
|
|
| 301 |
|
| 302 |
-
params = {}
|
| 303 |
if make_meet_link:
|
| 304 |
-
body["conferenceData"] = {
|
| 305 |
-
"createRequest": {"requestId": str(uuid4())}
|
| 306 |
-
}
|
| 307 |
params["conferenceDataVersion"] = 1
|
| 308 |
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
)
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
return f"
|
| 322 |
except Exception as e:
|
| 323 |
tool_log.exception("β schedule_meeting failed")
|
| 324 |
return f"Calendar error: {e}"
|
|
|
|
| 274 |
@tool("schedule_meeting")
|
| 275 |
def schedule_meeting(
|
| 276 |
title: str,
|
| 277 |
+
start_rfc3339: str,
|
| 278 |
+
end_rfc3339: str,
|
| 279 |
+
attendees: list[dict] | None = None,
|
| 280 |
+
description: str | None = None,
|
| 281 |
+
location: str | None = None,
|
| 282 |
calendar_id: str = "primary",
|
| 283 |
make_meet_link: bool = True,
|
| 284 |
+
send_updates: str = "all", # "all" | "externalOnly" | "none"
|
| 285 |
) -> str:
|
| 286 |
"""
|
| 287 |
Create a Google Calendar event (and optional Google Meet link).
|
| 288 |
Returns a human-readable confirmation.
|
| 289 |
"""
|
| 290 |
try:
|
|
|
|
| 291 |
svc = get_gcal_service()
|
| 292 |
+
tool_log.info(
|
| 293 |
+
"schedule_meeting args title=%s start=%s end=%s attendees=%s",
|
| 294 |
+
title, start_rfc3339, end_rfc3339,
|
| 295 |
+
[a.get("email") for a in (attendees or [])],
|
| 296 |
+
)
|
| 297 |
|
| 298 |
body = {
|
| 299 |
"summary": title,
|
|
|
|
| 301 |
"location": location or "",
|
| 302 |
"start": {"dateTime": start_rfc3339},
|
| 303 |
"end": {"dateTime": end_rfc3339},
|
| 304 |
+
"attendees": [{"email": a["email"], "optional": bool(a.get("optional", False))}
|
| 305 |
+
for a in (attendees or [])],
|
| 306 |
}
|
| 307 |
+
params = {"calendarId": calendar_id, "sendUpdates": send_updates}
|
| 308 |
|
|
|
|
| 309 |
if make_meet_link:
|
| 310 |
+
body["conferenceData"] = {"createRequest": {"requestId": str(uuid4())}}
|
|
|
|
|
|
|
| 311 |
params["conferenceDataVersion"] = 1
|
| 312 |
|
| 313 |
+
ev = svc.events().insert(**params, body=body).execute()
|
| 314 |
+
eid = ev.get("id")
|
| 315 |
+
hlink = ev.get("htmlLink")
|
| 316 |
+
tool_log.info("β
event created id=%s link=%s", eid, hlink)
|
| 317 |
+
|
| 318 |
+
# Extract meet url if present
|
| 319 |
+
meet_url = None
|
| 320 |
+
conf = ev.get("conferenceData") or {}
|
| 321 |
+
for ep in conf.get("entryPoints", []):
|
| 322 |
+
if ep.get("entryPointType") == "video":
|
| 323 |
+
meet_url = ep.get("uri"); break
|
| 324 |
+
|
| 325 |
+
if not eid:
|
| 326 |
+
return "Calendar error: event creation returned no id."
|
| 327 |
+
|
| 328 |
+
attendees_str = ", ".join([a["email"] for a in (attendees or [])]) or "β"
|
| 329 |
+
when = f'{ev["start"].get("dateTime") or ev["start"].get("date")} β {ev["end"].get("dateTime") or ev["end"].get("date")}'
|
| 330 |
+
return (
|
| 331 |
+
f"β
Scheduled: {title}\n"
|
| 332 |
+
f"π
When: {when}\n"
|
| 333 |
+
f"π₯ With: {attendees_str}\n"
|
| 334 |
+
f"π Meet: {meet_url or 'β'}\n"
|
| 335 |
+
f"ποΈ Calendar: {calendar_id}\n"
|
| 336 |
+
f"π View: {hlink or 'β'}\n"
|
| 337 |
+
f"π Event ID: {eid}"
|
| 338 |
)
|
| 339 |
+
except RuntimeError:
|
| 340 |
+
base = os.getenv("PUBLIC_BASE_URL", "").rstrip("/")
|
| 341 |
+
connect = f"{base}/oauth/google/start" if base else "/oauth/google/start"
|
| 342 |
+
return f"Google Calendar is not connected.\nβ Connect here: {connect}\nThen try again."
|
| 343 |
except Exception as e:
|
| 344 |
tool_log.exception("β schedule_meeting failed")
|
| 345 |
return f"Calendar error: {e}"
|
backend/api.py
CHANGED
|
@@ -80,6 +80,28 @@ def debug_oauth():
|
|
| 80 |
"base_url_effective": BASE_URL,
|
| 81 |
"redirect_uri_built": REDIRECT_URI,
|
| 82 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
@api.get("/debug/env")
|
| 85 |
def debug_env():
|
|
@@ -158,16 +180,30 @@ def resume_download():
|
|
| 158 |
filename="Krishna_Vamsi_Dhulipalla_Resume.pdf",
|
| 159 |
)
|
| 160 |
|
| 161 |
-
@api.get("/health/google")
|
| 162 |
def google_health():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
try:
|
| 164 |
svc = get_gcal_service()
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
except Exception as e:
|
| 170 |
-
log.exception("π΄ Google Calendar health failed")
|
| 171 |
return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
|
| 172 |
|
| 173 |
@api.get("/oauth/google/start")
|
|
|
|
| 80 |
"base_url_effective": BASE_URL,
|
| 81 |
"redirect_uri_built": REDIRECT_URI,
|
| 82 |
}
|
| 83 |
+
# backend/api.py
|
| 84 |
+
@api.get("/api/debug/google/scopes")
|
| 85 |
+
def debug_google_scopes():
|
| 86 |
+
try:
|
| 87 |
+
from google.oauth2.credentials import Credentials
|
| 88 |
+
from .g_cal import TOKEN_FILE
|
| 89 |
+
creds = Credentials.from_authorized_user_file(str(TOKEN_FILE))
|
| 90 |
+
return {"ok": True, "scopes": list(creds.scopes or [])}
|
| 91 |
+
except Exception as e:
|
| 92 |
+
return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
|
| 93 |
+
|
| 94 |
+
# backend/api.py
|
| 95 |
+
@api.post("/api/debug/google/reset")
|
| 96 |
+
def google_reset():
|
| 97 |
+
from .g_cal import TOKEN_FILE
|
| 98 |
+
try:
|
| 99 |
+
if TOKEN_FILE.exists():
|
| 100 |
+
TOKEN_FILE.unlink()
|
| 101 |
+
return {"ok": True, "message": "Token cleared. Reconnect at /oauth/google/start"}
|
| 102 |
+
except Exception as e:
|
| 103 |
+
return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
|
| 104 |
+
|
| 105 |
|
| 106 |
@api.get("/debug/env")
|
| 107 |
def debug_env():
|
|
|
|
| 180 |
filename="Krishna_Vamsi_Dhulipalla_Resume.pdf",
|
| 181 |
)
|
| 182 |
|
| 183 |
+
@api.get("/api/health/google")
|
| 184 |
def google_health():
|
| 185 |
+
"""
|
| 186 |
+
Minimal write+delete probe using only calendar.events:
|
| 187 |
+
- create a 1-minute event 5 minutes in the future (sendUpdates='none')
|
| 188 |
+
- delete it immediately
|
| 189 |
+
"""
|
| 190 |
try:
|
| 191 |
svc = get_gcal_service()
|
| 192 |
+
now = datetime.now(timezone.utc)
|
| 193 |
+
start = (now + timedelta(minutes=5)).isoformat(timespec="seconds")
|
| 194 |
+
end = (now + timedelta(minutes=6)).isoformat(timespec="seconds")
|
| 195 |
+
body = {"summary": "HF Health Probe",
|
| 196 |
+
"start": {"dateTime": start},
|
| 197 |
+
"end": {"dateTime": end}}
|
| 198 |
+
ev = svc.events().insert(
|
| 199 |
+
calendarId="primary", body=body, sendUpdates="none"
|
| 200 |
+
).execute()
|
| 201 |
+
eid = ev.get("id", "")
|
| 202 |
+
# clean up
|
| 203 |
+
if eid:
|
| 204 |
+
svc.events().delete(calendarId="primary", eventId=eid, sendUpdates="none").execute()
|
| 205 |
+
return {"ok": True, "probe": "insert+delete", "event_id": eid}
|
| 206 |
except Exception as e:
|
|
|
|
| 207 |
return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
|
| 208 |
|
| 209 |
@api.get("/oauth/google/start")
|