Spaces:
Runtime error
Runtime error
| from agentpro.tools import Tool | |
| from typing import Any | |
| import datetime | |
| import dateutil.parser | |
| import re | |
| class DailyEventSummaryTool(Tool): | |
| # ββ 1) Class attributes (Pydantic fields) βββββββββββββββββββββββββββ | |
| name: str = "Daily Event Summary" | |
| description: str = ( | |
| "Provide a narrative summary of Google Calendar events for any requested day. " | |
| "You can ask for today, tomorrow, day after tomorrow, last day of this year, " | |
| "a weekday of the current week (e.g., 'Wednesday'), or an explicit date like 'June 10, 2025'." | |
| ) | |
| action_type: str = "daily_event_summary" | |
| input_format: str = ( | |
| "Natural language calendar query. Examples:\n" | |
| " β’ \"Whatβs on my calendar today?\"\n" | |
| " β’ \"Show me my schedule tomorrow.\"\n" | |
| " β’ \"Plan for day after tomorrow.\"\n" | |
| " β’ \"Events on the last day of this year.\"\n" | |
| " β’ \"What do I have on Wednesday?\"\n" | |
| " β’ \"What do I have on June 10, 2025?\"\n" | |
| ) | |
| # ββ 2) We expect a Google Calendar βserviceβ to be passed in at instantiation ββ | |
| service: Any | |
| def run(self, input_text: Any) -> str: | |
| """ | |
| Determine which calendar day the user wants and return a single-sentence | |
| narrative describing each event's start time, end time, and duration. | |
| Supported queries: | |
| - βtodayβ | |
| - βtomorrowβ | |
| - βday after tomorrowβ | |
| - βlast day of this yearβ (Dec 31, current year) | |
| - any weekday of the current week (e.g., βMondayβ, βFridayβ) | |
| - explicit dates (e.g., βJune 10, 2025β or β2025-06-10β) | |
| """ | |
| text = str(input_text).lower() | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # A) Handle relative-day keywords and weekdays of this week | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| today_utc = datetime.datetime.utcnow().date() | |
| target_date = None | |
| # 1) βtodayβ | |
| if "today" in text: | |
| target_date = today_utc | |
| # 2) βtomorrowβ (but not βday after tomorrowβ) | |
| elif "tomorrow" in text and "day after" not in text: | |
| target_date = today_utc + datetime.timedelta(days=1) | |
| # 3) βday after tomorrowβ | |
| elif "day after tomorrow" in text: | |
| target_date = today_utc + datetime.timedelta(days=2) | |
| # 4) βlast day of this yearβ or βlast day of the yearβ | |
| elif "last day of this year" in text or "last day of the year" in text: | |
| year = today_utc.year | |
| target_date = datetime.date(year, 12, 31) | |
| else: | |
| # 5) Try to match a weekday name in the current week | |
| weekdays = { | |
| "monday": 1, | |
| "tuesday": 2, | |
| "wednesday": 3, | |
| "thursday": 4, | |
| "friday": 5, | |
| "saturday": 6, | |
| "sunday": 7 | |
| } | |
| for name, iso_num in weekdays.items(): | |
| if name in text: | |
| # Compute offset from today's ISO weekday to the requested one | |
| today_iso = today_utc.isoweekday() # Monday=1 ... Sunday=7 | |
| delta_days = iso_num - today_iso | |
| target_date = today_utc + datetime.timedelta(days=delta_days) | |
| break | |
| # 6) If still None, try to parse an explicit date | |
| if target_date is None and re.search(r"\d", text): | |
| try: | |
| parsed_dt = dateutil.parser.parse(text, fuzzy=True) | |
| target_date = parsed_dt.date() | |
| except (ValueError, OverflowError): | |
| target_date = None | |
| # If we still don't have a date, return fallback instructions | |
| if target_date is None: | |
| return ( | |
| "Sorry, I couldnβt figure out which day you mean.\n" | |
| "Please ask for:\n" | |
| " β’ βtodayβ\n" | |
| " β’ βtomorrowβ\n" | |
| " β’ βday after tomorrowβ\n" | |
| " β’ βlast day of this yearβ\n" | |
| " β’ a weekday this week (e.g., βWednesdayβ)\n" | |
| " β’ or specify an explicit date (e.g., βJune 10, 2025β)." | |
| ) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # B) Build UTCβbased timestamps for that entire day: [00:00 β next 00:00) | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| start_of_day = datetime.datetime.combine(target_date, datetime.time.min).isoformat() + "Z" | |
| end_of_day = ( | |
| datetime.datetime.combine(target_date, datetime.time.min) | |
| + datetime.timedelta(days=1) | |
| ).isoformat() + "Z" | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # C) Query Google Calendar API for events in that window | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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", []) | |
| if not items: | |
| return f"You have no events scheduled for {target_date.strftime('%B %d, %Y')}." | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # D) Build narrative: βOn {date}, first meeting is βXβ, which will start at {h:mm AM/PM} | |
| # and end at {h:mm AM/PM} (Duration: {N hours M minutes}), and second meeting is β¦.β | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ordinals = [ | |
| "first", "second", "third", "fourth", "fifth", | |
| "sixth", "seventh", "eighth", "ninth", "tenth" | |
| ] | |
| narrative_parts = [] | |
| for idx, ev in enumerate(items): | |
| start_raw = ev["start"].get("dateTime") | |
| end_raw = ev["end"].get("dateTime") | |
| summary = ev.get("summary", "(no title)") | |
| if start_raw and end_raw: | |
| # Timed event | |
| start_dt = datetime.datetime.fromisoformat(start_raw.replace("Z", "+00:00")) | |
| end_dt = datetime.datetime.fromisoformat(end_raw.replace("Z", "+00:00")) | |
| # Format βH:MM AM/PMβ | |
| start_str = start_dt.strftime("%I:%M %p").lstrip("0") | |
| end_str = end_dt.strftime("%I:%M %p").lstrip("0") | |
| # Compute duration | |
| duration_ts = end_dt - start_dt | |
| total_seconds = int(duration_ts.total_seconds()) | |
| hours = total_seconds // 3600 | |
| minutes = (total_seconds % 3600) // 60 | |
| if hours and minutes: | |
| duration_str = f"{hours} hours {minutes} minutes" | |
| elif hours: | |
| duration_str = f"{hours} hours" | |
| else: | |
| duration_str = f"{minutes} minutes" | |
| ordinal = ordinals[idx] if idx < len(ordinals) else f"{idx+1}th" | |
| part = ( | |
| f"{ordinal} meeting is β{summary},β which will start at {start_str} " | |
| f"and end at {end_str} (Duration: {duration_str})" | |
| ) | |
| else: | |
| # All-day event | |
| start_date = ev["start"].get("date") | |
| ordinal = ordinals[idx] if idx < len(ordinals) else f"{idx+1}th" | |
| part = f"{ordinal} event is β{summary},β which is an all-day event on {start_date}" | |
| narrative_parts.append(part) | |
| # Join all parts with β, and β¦β | |
| joined = ", and ".join(narrative_parts) | |
| date_str = target_date.strftime("%B %d, %Y") | |
| return f"On {date_str}, {joined}." |