Spaces:
Runtime error
Runtime error
from agentpro.tools import Tool | |
from typing import Any, List, Tuple, Optional | |
import datetime | |
import dateutil.parser | |
import re | |
class WeeklyEventSummaryTool(Tool): | |
""" | |
Tool to provide a narrative summary of Google Calendar events for an entire week. | |
Users can ask for βthis week,β βnext week,β βlast week,β or specify a date (e.g., βweek of June 10, 2025β). | |
The tool will gather events from Monday through Sunday of the chosen week and return a day-by-day narrative. | |
""" | |
# ββ 1) Class attributes βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
name: str = "Weekly Event Summary" | |
description: str = ( | |
"Provide a narrative summary of Google Calendar events for any requested week. " | |
"You can ask for:\n" | |
" β’ βMy events this week.β\n" | |
" β’ βShow my schedule next week.β\n" | |
" β’ βWeekly summary for last week.β\n" | |
" β’ βWhat do I have the week of June 10, 2025?β\n" | |
" β’ βEvents for week of 2025-06-10.β\n" | |
"If no explicit week is mentioned, defaults to this week (MondayβSunday)." | |
) | |
action_type: str = "weekly_event_summary" | |
input_format: str = ( | |
"Natural-language calendar query specifying a week. Examples:\n" | |
" β’ βWhatβs on my calendar this week?β\n" | |
" β’ βShow me my schedule next week.β\n" | |
" β’ βWeekly summary for last week.β\n" | |
" β’ βEvents for the week of June 10, 2025.β\n" | |
" β’ βWeek of 2025-06-10.β" | |
) | |
# ββ 2) We expect a Google Calendar βserviceβ to be passed in at instantiation βββββββββββ | |
service: Any | |
# ββ 3) Main entry point βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
def run(self, input_text: Any) -> str: | |
""" | |
Parse a natural-language weekly summary command, identify target week, and return a narrative summary. | |
""" | |
text = str(input_text).strip().lower() | |
# 1) Determine which week the user means | |
week_start = self._resolve_week_start(text) | |
if week_start is None: | |
return ( | |
"Sorry, I couldn't determine which week you meant. " | |
"Please ask for 'this week', 'next week', 'last week', or 'week of <date>'." | |
) | |
# 2) Build a list of dates from Monday through Sunday | |
dates = [week_start + datetime.timedelta(days=i) for i in range(7)] | |
week_end = dates[-1] | |
# 3) For each day in the week, fetch events and build narrative parts | |
narrative_parts: List[str] = [] | |
for day in dates: | |
events = self._fetch_events_on_date(day) | |
day_str = day.strftime("%A, %B %d, %Y") | |
if not events: | |
narrative_parts.append(f"On {day_str}, you have no events scheduled.") | |
else: | |
# Build a single sentence listing each eventβs start time, end time, and title | |
sentences: List[str] = [] | |
for idx, ev in enumerate(events): | |
start_raw = ev["start"].get("dateTime") | |
end_raw = ev["end"].get("dateTime") | |
summary = ev.get("summary", "(no title)") | |
if start_raw and end_raw: | |
start_dt = datetime.datetime.fromisoformat(start_raw.replace("Z", "+00:00")) | |
end_dt = datetime.datetime.fromisoformat(end_raw.replace("Z", "+00:00")) | |
start_str = start_dt.strftime("%I:%M %p").lstrip("0") | |
end_str = end_dt.strftime("%I:%M %p").lstrip("0") | |
sentences.append(f"β{summary}β from {start_str} to {end_str}") | |
else: | |
# Should not happen for non-all-day events since we filter them | |
sentences.append(f"β{summary}β (all-day)") | |
# Join individual event descriptions with β; β | |
day_events_str = "; ".join(sentences) | |
narrative_parts.append(f"On {day_str}, you have: {day_events_str}.") | |
# 4) Combine into one multiline narrative | |
week_range_str = f"{week_start.strftime('%B %d, %Y')} to {week_end.strftime('%B %d, %Y')}" | |
header = f"Weekly summary for {week_range_str}:" | |
body = " ".join(narrative_parts) | |
return f"{header}\n\n{body}" | |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
def _resolve_week_start(self, text: str) -> Optional[datetime.date]: | |
""" | |
Determine the Monday (week_start) of the requested week. | |
Supports 'this week', 'next week', 'last week', or 'week of <date>'. | |
If no keyword found, defaults to this week. | |
""" | |
today = datetime.date.today() | |
weekday = today.weekday() # Monday=0 ... Sunday=6 | |
monday_this_week = today - datetime.timedelta(days=weekday) | |
# 1) Check for 'this week' | |
if "this week" in text: | |
return monday_this_week | |
# 2) 'next week' | |
if "next week" in text: | |
return monday_this_week + datetime.timedelta(days=7) | |
# 3) 'last week' | |
if "last week" in text: | |
return monday_this_week - datetime.timedelta(days=7) | |
# 4) 'week of <date>' | |
# Look for a date substring to parse | |
# e.g., "week of june 10, 2025" or "week of 2025-06-10" | |
match = re.search( | |
r"week\s+of\s+(.+)", text | |
) | |
if match: | |
date_part = match.group(1).strip() | |
try: | |
parsed = dateutil.parser.parse(date_part, fuzzy=True) | |
target_date = parsed.date() | |
# Find Monday of that week | |
wd = target_date.weekday() | |
return target_date - datetime.timedelta(days=wd) | |
except (ValueError, OverflowError): | |
return None | |
# 5) If no explicit keyword, default to this week | |
return monday_this_week | |
def _fetch_events_on_date(self, date_obj: datetime.date) -> List[dict]: | |
""" | |
Fetch all non-all-day events on the provided date (UTC midnight β next midnight). | |
Returns a list of event dicts (as returned by Google Calendar API), sorted by start time. | |
""" | |
start_of_day = datetime.datetime.combine(date_obj, datetime.time.min).isoformat() + "Z" | |
end_of_day = (datetime.datetime.combine(date_obj, datetime.time.min) | |
+ datetime.timedelta(days=1)).isoformat() + "Z" | |
events_res = ( | |
self.service.events() | |
.list( | |
calendarId="primary", | |
timeMin=start_of_day, | |
timeMax=end_of_day, | |
singleEvents=True, | |
orderBy="startTime" | |
) | |
.execute() | |
) | |
items = events_res.get("items", []) | |
# Filter out all-day events (they have 'start.date' instead of 'start.dateTime') | |
non_all_day = [ev for ev in items if ev.get("start", {}).get("dateTime")] | |
return sorted( | |
non_all_day, | |
key=lambda ev: datetime.datetime.fromisoformat(ev["start"]["dateTime"].replace("Z", "+00:00")) | |
) | |