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")) | |
| ) | |