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}." |