File size: 9,507 Bytes
465fb49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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}."