agentr 0.1.7__py3-none-any.whl → 0.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,11 +14,6 @@ class GoogleCalendarApp(APIApplication):
14
14
  if not self.integration:
15
15
  raise ValueError("Integration not configured for GoogleCalendarApp")
16
16
  credentials = self.integration.get_credentials()
17
- if not credentials:
18
- logger.warning("No Google Calendar credentials found via integration.")
19
- action = self.integration.authorize()
20
- raise NotAuthorizedError(action)
21
-
22
17
  if "headers" in credentials:
23
18
  return credentials["headers"]
24
19
  return {
@@ -71,82 +66,72 @@ class GoogleCalendarApp(APIApplication):
71
66
  Returns:
72
67
  A formatted list of events or an error message
73
68
  """
74
- try:
75
- # Get today's date in ISO format
76
- today = datetime.utcnow().date()
77
- end_date = today + timedelta(days=days)
78
-
79
- # Format dates for API
80
- time_min = f"{today.isoformat()}T00:00:00Z"
81
- time_max = f"{end_date.isoformat()}T00:00:00Z"
82
-
83
- url = f"{self.base_api_url}/events"
84
-
85
- # Build query parameters
86
- params = {
87
- "timeMin": time_min,
88
- "timeMax": time_max,
89
- "singleEvents": "true",
90
- "orderBy": "startTime"
91
- }
92
-
93
- if max_results is not None:
94
- params["maxResults"] = max_results
95
-
96
- if time_zone:
97
- params["timeZone"] = time_zone
98
-
99
- date_range = "today" if days == 1 else f"the next {days} days"
100
- logger.info(f"Retrieving calendar events for {date_range}")
101
-
102
- response = self._get(url, params=params)
69
+ # Get today's date in ISO format
70
+ today = datetime.utcnow().date()
71
+ end_date = today + timedelta(days=days)
72
+
73
+ # Format dates for API
74
+ time_min = f"{today.isoformat()}T00:00:00Z"
75
+ time_max = f"{end_date.isoformat()}T00:00:00Z"
76
+
77
+ url = f"{self.base_api_url}/events"
78
+
79
+ # Build query parameters
80
+ params = {
81
+ "timeMin": time_min,
82
+ "timeMax": time_max,
83
+ "singleEvents": "true",
84
+ "orderBy": "startTime"
85
+ }
86
+
87
+ if max_results is not None:
88
+ params["maxResults"] = max_results
103
89
 
104
- if response.status_code == 200:
105
- events = response.json().get("items", [])
106
- if not events:
107
- return f"No events scheduled for {date_range}."
108
-
109
- result = f"Events for {date_range}:\n\n"
110
- for event in events:
111
- # Extract event date and time
112
- start = event.get("start", {})
113
- event_date = start.get("date", start.get("dateTime", "")).split("T")[0] if "T" in start.get("dateTime", "") else start.get("date", "")
114
-
115
- # Extract and format time
116
- start_time = start.get("dateTime", start.get("date", "All day"))
117
-
118
- # Format the time display
119
- if "T" in start_time: # It's a datetime
120
- formatted_time = self._format_datetime(start_time)
121
- # For multi-day view, keep the date; for single day, just show time
122
- if days > 1:
123
- time_display = formatted_time
124
- else:
125
- # Extract just the time part
126
- time_display = formatted_time.split(" ")[1] + " " + formatted_time.split(" ")[2]
127
- else: # It's an all-day event
128
- if days > 1:
129
- time_display = f"{event_date} (All day)"
130
- else:
131
- time_display = "All day"
132
-
133
- # Get event details
134
- summary = event.get("summary", "Untitled event")
135
- event_id = event.get("id", "No ID")
136
-
137
- result += f"- {time_display}: {summary} (ID: {event_id})\n"
138
-
139
- return result
140
- else:
141
- logger.error(f"Google Calendar API Error: {response.status_code} - {response.text}")
142
- return f"Error retrieving calendar events: {response.status_code} - {response.text}"
143
- except NotAuthorizedError as e:
144
- logger.warning(f"Google Calendar authorization required: {e.message}")
145
- return e.message
146
- except Exception as e:
147
- logger.exception(f"Error retrieving calendar events: {type(e).__name__} - {str(e)}")
148
- return f"Error retrieving calendar events: {type(e).__name__} - {str(e)}"
149
-
90
+ if time_zone:
91
+ params["timeZone"] = time_zone
92
+
93
+ date_range = "today" if days == 1 else f"the next {days} days"
94
+ logger.info(f"Retrieving calendar events for {date_range}")
95
+
96
+ response = self._get(url, params=params)
97
+ response.raise_for_status()
98
+
99
+ events = response.json().get("items", [])
100
+ if not events:
101
+ return f"No events scheduled for {date_range}."
102
+
103
+ result = f"Events for {date_range}:\n\n"
104
+ for event in events:
105
+ # Extract event date and time
106
+ start = event.get("start", {})
107
+ event_date = start.get("date", start.get("dateTime", "")).split("T")[0] if "T" in start.get("dateTime", "") else start.get("date", "")
108
+
109
+ # Extract and format time
110
+ start_time = start.get("dateTime", start.get("date", "All day"))
111
+
112
+ # Format the time display
113
+ if "T" in start_time: # It's a datetime
114
+ formatted_time = self._format_datetime(start_time)
115
+ # For multi-day view, keep the date; for single day, just show time
116
+ if days > 1:
117
+ time_display = formatted_time
118
+ else:
119
+ # Extract just the time part
120
+ time_display = formatted_time.split(" ")[1] + " " + formatted_time.split(" ")[2]
121
+ else: # It's an all-day event
122
+ if days > 1:
123
+ time_display = f"{event_date} (All day)"
124
+ else:
125
+ time_display = "All day"
126
+
127
+ # Get event details
128
+ summary = event.get("summary", "Untitled event")
129
+ event_id = event.get("id", "No ID")
130
+
131
+ result += f"- {time_display}: {summary} (ID: {event_id})\n"
132
+
133
+ return result
134
+
150
135
  def get_event(self, event_id: str, max_attendees: int = None, time_zone: str = None) -> str:
151
136
  """Get a specific event from your Google Calendar by ID
152
137
 
@@ -158,89 +143,77 @@ class GoogleCalendarApp(APIApplication):
158
143
  Returns:
159
144
  A formatted event details or an error message
160
145
  """
161
- try:
162
- url = f"{self.base_api_url}/events/{event_id}"
163
-
164
- # Build query parameters
165
- params = {}
166
- if max_attendees is not None:
167
- params["maxAttendees"] = max_attendees
168
- if time_zone:
169
- params["timeZone"] = time_zone
170
-
171
- logger.info(f"Retrieving calendar event with ID: {event_id}")
172
-
173
- response = self._get(url, params=params)
174
-
175
- if response.status_code == 200:
176
- event = response.json()
177
-
178
- # Extract event details
179
- summary = event.get("summary", "Untitled event")
180
- description = event.get("description", "No description")
181
- location = event.get("location", "No location specified")
182
-
183
- # Format dates
184
- start = event.get("start", {})
185
- end = event.get("end", {})
186
-
187
- start_time = start.get("dateTime", start.get("date", "Unknown"))
188
- end_time = end.get("dateTime", end.get("date", "Unknown"))
189
-
190
- # Format datetimes using the helper function
191
- start_formatted = self._format_datetime(start_time)
192
- end_formatted = self._format_datetime(end_time)
193
-
194
- # Get creator and organizer
195
- creator = event.get("creator", {}).get("email", "Unknown")
196
- organizer = event.get("organizer", {}).get("email", "Unknown")
197
-
198
- # Check if it's a recurring event
199
- recurrence = "Yes" if "recurrence" in event else "No"
200
-
201
- # Get attendees if any
202
- attendees = event.get("attendees", [])
203
- attendee_info = ""
204
- if attendees:
205
- attendee_info = "\nAttendees:\n"
206
- for i, attendee in enumerate(attendees, 1):
207
- email = attendee.get("email", "No email")
208
- name = attendee.get("displayName", email)
209
- response_status = attendee.get("responseStatus", "Unknown")
210
-
211
- status_mapping = {
212
- "accepted": "Accepted",
213
- "declined": "Declined",
214
- "tentative": "Maybe",
215
- "needsAction": "Not responded"
216
- }
217
-
218
- formatted_status = status_mapping.get(response_status, response_status)
219
- attendee_info += f" {i}. {name} ({email}) - {formatted_status}\n"
220
-
221
- # Format the response
222
- result = f"Event: {summary}\n"
223
- result += f"ID: {event_id}\n"
224
- result += f"When: {start_formatted} to {end_formatted}\n"
225
- result += f"Where: {location}\n"
226
- result += f"Description: {description}\n"
227
- result += f"Creator: {creator}\n"
228
- result += f"Organizer: {organizer}\n"
229
- result += f"Recurring: {recurrence}\n"
230
- result += attendee_info
231
-
232
- return result
233
- elif response.status_code == 404:
234
- return f"Event not found with ID: {event_id}"
235
- else:
236
- logger.error(f"Google Calendar API Error: {response.status_code} - {response.text}")
237
- return f"Error retrieving event: {response.status_code} - {response.text}"
238
- except NotAuthorizedError as e:
239
- logger.warning(f"Google Calendar authorization required: {e.message}")
240
- return e.message
241
- except Exception as e:
242
- logger.exception(f"Error retrieving event: {type(e).__name__} - {str(e)}")
243
- return f"Error retrieving event: {type(e).__name__} - {str(e)}"
146
+ url = f"{self.base_api_url}/events/{event_id}"
147
+
148
+ # Build query parameters
149
+ params = {}
150
+ if max_attendees is not None:
151
+ params["maxAttendees"] = max_attendees
152
+ if time_zone:
153
+ params["timeZone"] = time_zone
154
+
155
+ logger.info(f"Retrieving calendar event with ID: {event_id}")
156
+
157
+ response = self._get(url, params=params)
158
+ response.raise_for_status()
159
+
160
+ event = response.json()
161
+
162
+ # Extract event details
163
+ summary = event.get("summary", "Untitled event")
164
+ description = event.get("description", "No description")
165
+ location = event.get("location", "No location specified")
166
+
167
+ # Format dates
168
+ start = event.get("start", {})
169
+ end = event.get("end", {})
170
+
171
+ start_time = start.get("dateTime", start.get("date", "Unknown"))
172
+ end_time = end.get("dateTime", end.get("date", "Unknown"))
173
+
174
+ # Format datetimes using the helper function
175
+ start_formatted = self._format_datetime(start_time)
176
+ end_formatted = self._format_datetime(end_time)
177
+
178
+ # Get creator and organizer
179
+ creator = event.get("creator", {}).get("email", "Unknown")
180
+ organizer = event.get("organizer", {}).get("email", "Unknown")
181
+
182
+ # Check if it's a recurring event
183
+ recurrence = "Yes" if "recurrence" in event else "No"
184
+
185
+ # Get attendees if any
186
+ attendees = event.get("attendees", [])
187
+ attendee_info = ""
188
+ if attendees:
189
+ attendee_info = "\nAttendees:\n"
190
+ for i, attendee in enumerate(attendees, 1):
191
+ email = attendee.get("email", "No email")
192
+ name = attendee.get("displayName", email)
193
+ response_status = attendee.get("responseStatus", "Unknown")
194
+
195
+ status_mapping = {
196
+ "accepted": "Accepted",
197
+ "declined": "Declined",
198
+ "tentative": "Maybe",
199
+ "needsAction": "Not responded"
200
+ }
201
+
202
+ formatted_status = status_mapping.get(response_status, response_status)
203
+ attendee_info += f" {i}. {name} ({email}) - {formatted_status}\n"
204
+
205
+ # Format the response
206
+ result = f"Event: {summary}\n"
207
+ result += f"ID: {event_id}\n"
208
+ result += f"When: {start_formatted} to {end_formatted}\n"
209
+ result += f"Where: {location}\n"
210
+ result += f"Description: {description}\n"
211
+ result += f"Creator: {creator}\n"
212
+ result += f"Organizer: {organizer}\n"
213
+ result += f"Recurring: {recurrence}\n"
214
+ result += attendee_info
215
+
216
+ return result
244
217
 
245
218
  def list_events(self, max_results: int = 10, time_min: str = None, time_max: str = None,
246
219
  q: str = None, order_by: str = "startTime", single_events: bool = True,
@@ -260,100 +233,90 @@ class GoogleCalendarApp(APIApplication):
260
233
  Returns:
261
234
  A formatted list of events or an error message
262
235
  """
263
- try:
264
- url = f"{self.base_api_url}/events"
265
-
266
- # Build query parameters
267
- params = {
268
- "maxResults": max_results,
269
- "singleEvents": str(single_events).lower(),
270
- "orderBy": order_by
271
- }
236
+ url = f"{self.base_api_url}/events"
237
+
238
+ # Build query parameters
239
+ params = {
240
+ "maxResults": max_results,
241
+ "singleEvents": str(single_events).lower(),
242
+ "orderBy": order_by
243
+ }
244
+
245
+ # Set time boundaries if provided, otherwise default to now for time_min
246
+ if time_min:
247
+ params["timeMin"] = time_min
248
+ else:
249
+ # Default to current time if not specified
250
+ now = datetime.utcnow().isoformat() + "Z" # 'Z' indicates UTC time
251
+ params["timeMin"] = now
272
252
 
273
- # Set time boundaries if provided, otherwise default to now for time_min
274
- if time_min:
275
- params["timeMin"] = time_min
276
- else:
277
- # Default to current time if not specified
278
- now = datetime.utcnow().isoformat() + "Z" # 'Z' indicates UTC time
279
- params["timeMin"] = now
280
-
281
- if time_max:
282
- params["timeMax"] = time_max
283
-
284
- # Add optional filters if provided
285
- if q:
286
- params["q"] = q
287
-
288
- if time_zone:
289
- params["timeZone"] = time_zone
290
-
291
- if page_token:
292
- params["pageToken"] = page_token
253
+ if time_max:
254
+ params["timeMax"] = time_max
293
255
 
294
- logger.info(f"Retrieving calendar events with params: {params}")
256
+ # Add optional filters if provided
257
+ if q:
258
+ params["q"] = q
295
259
 
296
- response = self._get(url, params=params)
260
+ if time_zone:
261
+ params["timeZone"] = time_zone
297
262
 
298
- if response.status_code == 200:
299
- data = response.json()
300
- events = data.get("items", [])
301
-
302
- if not events:
303
- return "No events found matching your criteria."
304
-
305
- # Extract calendar information
306
- calendar_summary = data.get("summary", "Your Calendar")
307
- time_zone_info = data.get("timeZone", "Unknown")
308
-
309
- result = f"Events from {calendar_summary} (Time Zone: {time_zone_info}):\n\n"
310
-
311
- # Process and format each event
312
- for i, event in enumerate(events, 1):
313
- # Get basic event details
314
- event_id = event.get("id", "No ID")
315
- summary = event.get("summary", "Untitled event")
316
-
317
- # Get event times and format them
318
- start = event.get("start", {})
319
- start_time = start.get("dateTime", start.get("date", "Unknown"))
320
-
321
- # Format the start time using the helper function
322
- start_formatted = self._format_datetime(start_time)
323
-
324
- # Get location if available
325
- location = event.get("location", "No location specified")
326
-
327
- # Check if it's a recurring event
328
- is_recurring = "recurrence" in event
329
- recurring_info = " (Recurring)" if is_recurring else ""
330
-
331
- # Format the event information
332
- result += f"{i}. {summary}{recurring_info}\n"
333
- result += f" ID: {event_id}\n"
334
- result += f" When: {start_formatted}\n"
335
- result += f" Where: {location}\n"
336
-
337
- # Add a separator between events
338
- if i < len(events):
339
- result += "\n"
340
-
341
- # Add pagination info if available
342
- if "nextPageToken" in data:
343
- next_token = data.get("nextPageToken")
344
- result += f"\nMore events available. Use page_token='{next_token}' to see more."
345
-
346
- return result
347
- else:
348
- logger.error(f"Google Calendar API Error: {response.status_code} - {response.text}")
349
- return f"Error retrieving events: {response.status_code} - {response.text}"
350
- except NotAuthorizedError as e:
351
- logger.warning(f"Google Calendar authorization required: {e.message}")
352
- return e.message
353
- except Exception as e:
354
- logger.exception(f"Error retrieving events: {type(e).__name__} - {str(e)}")
355
- return f"Error retrieving events: {type(e).__name__} - {str(e)}"
356
-
263
+ if page_token:
264
+ params["pageToken"] = page_token
265
+
266
+ logger.info(f"Retrieving calendar events with params: {params}")
267
+
268
+ response = self._get(url, params=params)
269
+ response.raise_for_status()
270
+
271
+ data = response.json()
272
+ events = data.get("items", [])
273
+
274
+ if not events:
275
+ return "No events found matching your criteria."
276
+
277
+ # Extract calendar information
278
+ calendar_summary = data.get("summary", "Your Calendar")
279
+ time_zone_info = data.get("timeZone", "Unknown")
280
+
281
+ result = f"Events from {calendar_summary} (Time Zone: {time_zone_info}):\n\n"
282
+
283
+ # Process and format each event
284
+ for i, event in enumerate(events, 1):
285
+ # Get basic event details
286
+ event_id = event.get("id", "No ID")
287
+ summary = event.get("summary", "Untitled event")
288
+
289
+ # Get event times and format them
290
+ start = event.get("start", {})
291
+ start_time = start.get("dateTime", start.get("date", "Unknown"))
292
+
293
+ # Format the start time using the helper function
294
+ start_formatted = self._format_datetime(start_time)
295
+
296
+ # Get location if available
297
+ location = event.get("location", "No location specified")
298
+
299
+ # Check if it's a recurring event
300
+ is_recurring = "recurrence" in event
301
+ recurring_info = " (Recurring)" if is_recurring else ""
302
+
303
+ # Format the event information
304
+ result += f"{i}. {summary}{recurring_info}\n"
305
+ result += f" ID: {event_id}\n"
306
+ result += f" When: {start_formatted}\n"
307
+ result += f" Where: {location}\n"
308
+
309
+ # Add a separator between events
310
+ if i < len(events):
311
+ result += "\n"
312
+
313
+ # Add pagination info if available
314
+ if "nextPageToken" in data:
315
+ next_token = data.get("nextPageToken")
316
+ result += f"\nMore events available. Use page_token='{next_token}' to see more."
317
+
318
+ return result
319
+
357
320
  def quick_add_event(self, text: str, send_updates: str = "none") -> str:
358
321
  """Create a calendar event using natural language description
359
322
 
@@ -367,66 +330,56 @@ class GoogleCalendarApp(APIApplication):
367
330
  Returns:
368
331
  A confirmation message with the created event details or an error message
369
332
  """
370
- try:
371
- url = f"{self.base_api_url}/events/quickAdd"
372
-
373
- # Use params argument instead of manually constructing URL
374
- params = {
375
- "text": text,
376
- "sendUpdates": send_updates
377
- }
378
-
379
- logger.info(f"Creating event via quickAdd: '{text}'")
380
-
381
- # Pass params to _post method
382
- response = self._post(url, data=None, params=params)
333
+ url = f"{self.base_api_url}/events/quickAdd"
334
+
335
+ # Use params argument instead of manually constructing URL
336
+ params = {
337
+ "text": text,
338
+ "sendUpdates": send_updates
339
+ }
340
+
341
+ logger.info(f"Creating event via quickAdd: '{text}'")
342
+
343
+ # Pass params to _post method
344
+ response = self._post(url, data=None, params=params)
345
+ response.raise_for_status()
346
+
347
+ event = response.json()
348
+
349
+ # Extract event details
350
+ event_id = event.get("id", "Unknown")
351
+ summary = event.get("summary", "Untitled event")
352
+
353
+ # Format dates
354
+ start = event.get("start", {})
355
+ end = event.get("end", {})
356
+
357
+ start_time = start.get("dateTime", start.get("date", "Unknown"))
358
+ end_time = end.get("dateTime", end.get("date", "Unknown"))
359
+
360
+ # Format datetimes using the helper function
361
+ start_formatted = self._format_datetime(start_time)
362
+ end_formatted = self._format_datetime(end_time)
363
+
364
+ # Get location if available
365
+ location = event.get("location", "No location specified")
366
+
367
+ # Format the confirmation message
368
+ result = f"Successfully created event!\n\n"
369
+ result += f"Summary: {summary}\n"
370
+ result += f"When: {start_formatted}"
371
+
372
+ # Only add end time if it's different from start (for all-day events they might be the same)
373
+ if start_formatted != end_formatted:
374
+ result += f" to {end_formatted}"
383
375
 
384
- if response.status_code in [200, 201]:
385
- event = response.json()
386
-
387
- # Extract event details
388
- event_id = event.get("id", "Unknown")
389
- summary = event.get("summary", "Untitled event")
390
-
391
- # Format dates
392
- start = event.get("start", {})
393
- end = event.get("end", {})
394
-
395
- start_time = start.get("dateTime", start.get("date", "Unknown"))
396
- end_time = end.get("dateTime", end.get("date", "Unknown"))
397
-
398
- # Format datetimes using the helper function
399
- start_formatted = self._format_datetime(start_time)
400
- end_formatted = self._format_datetime(end_time)
401
-
402
- # Get location if available
403
- location = event.get("location", "No location specified")
404
-
405
- # Format the confirmation message
406
- result = f"Successfully created event!\n\n"
407
- result += f"Summary: {summary}\n"
408
- result += f"When: {start_formatted}"
409
-
410
- # Only add end time if it's different from start (for all-day events they might be the same)
411
- if start_formatted != end_formatted:
412
- result += f" to {end_formatted}"
413
-
414
- result += f"\nWhere: {location}\n"
415
- result += f"Event ID: {event_id}\n"
416
-
417
- # Add a note about viewing the event
418
- result += f"\nUse get_event('{event_id}') to see full details."
419
-
420
- return result
421
- else:
422
- logger.error(f"Google Calendar API Error: {response.status_code} - {response.text}")
423
- return f"Error creating event: {response.status_code} - {response.text}"
424
- except NotAuthorizedError as e:
425
- logger.warning(f"Google Calendar authorization required: {e.message}")
426
- return e.message
427
- except Exception as e:
428
- logger.exception(f"Error creating event: {type(e).__name__} - {str(e)}")
429
- return f"Error creating event: {type(e).__name__} - {str(e)}"
376
+ result += f"\nWhere: {location}\n"
377
+ result += f"Event ID: {event_id}\n"
378
+
379
+ # Add a note about viewing the event
380
+ result += f"\nUse get_event('{event_id}') to see full details."
381
+
382
+ return result
430
383
 
431
384
  def get_event_instances(self, event_id: str, max_results: int = 25, time_min: str = None,
432
385
  time_max: str = None, time_zone: str = None, show_deleted: bool = False,
@@ -447,102 +400,90 @@ class GoogleCalendarApp(APIApplication):
447
400
  Returns:
448
401
  A formatted list of event instances or an error message
449
402
  """
450
- try:
451
- url = f"{self.base_api_url}/events/{event_id}/instances"
452
-
453
- # Build query parameters
454
- params = {
455
- "maxResults": max_results,
456
- "showDeleted": str(show_deleted).lower()
457
- }
458
-
459
- # Add optional parameters if provided
460
- if time_min:
461
- params["timeMin"] = time_min
462
-
463
- if time_max:
464
- params["timeMax"] = time_max
465
-
466
- if time_zone:
467
- params["timeZone"] = time_zone
468
-
469
- if page_token:
470
- params["pageToken"] = page_token
471
-
472
- logger.info(f"Retrieving instances of recurring event with ID: {event_id}")
473
-
474
- response = self._get(url, params=params)
475
-
476
- if response.status_code == 200:
477
- data = response.json()
478
- instances = data.get("items", [])
479
-
480
- if not instances:
481
- return f"No instances found for recurring event with ID: {event_id}"
482
-
483
- # Extract event summary from the first instance
484
- parent_summary = instances[0].get("summary", "Untitled recurring event")
485
-
486
- result = f"Instances of recurring event: {parent_summary}\n\n"
487
-
488
- # Process and format each instance
489
- for i, instance in enumerate(instances, 1):
490
- # Get instance ID and status
491
- instance_id = instance.get("id", "No ID")
492
- status = instance.get("status", "confirmed")
493
-
494
- # Format status for display
495
- status_display = ""
496
- if status == "cancelled":
497
- status_display = " [CANCELLED]"
498
- elif status == "tentative":
499
- status_display = " [TENTATIVE]"
500
-
501
- # Get instance time
502
- start = instance.get("start", {})
503
- original_start_time = instance.get("originalStartTime", {})
504
-
505
- # Determine if this is a modified instance
506
- is_modified = original_start_time and "dateTime" in original_start_time
507
- modified_indicator = " [MODIFIED]" if is_modified else ""
508
-
509
- # Get the time information
510
- start_time = start.get("dateTime", start.get("date", "Unknown"))
511
-
512
- # Format the time using the helper function
513
- formatted_time = self._format_datetime(start_time)
514
-
515
- # Format the instance information
516
- result += f"{i}. {formatted_time}{status_display}{modified_indicator}\n"
517
- result += f" Instance ID: {instance_id}\n"
518
-
519
- # Show original start time if modified
520
- if is_modified:
521
- orig_time = original_start_time.get("dateTime", original_start_time.get("date", "Unknown"))
522
- orig_formatted = self._format_datetime(orig_time)
523
- result += f" Original time: {orig_formatted}\n"
524
-
525
- # Add a separator between instances
526
- if i < len(instances):
527
- result += "\n"
528
-
529
- # Add pagination info if available
530
- if "nextPageToken" in data:
531
- next_token = data.get("nextPageToken")
532
- result += f"\nMore instances available. Use page_token='{next_token}' to see more."
533
-
534
- return result
535
- elif response.status_code == 404:
536
- return f"Error: Event with ID {event_id} not found or is not a recurring event."
537
- else:
538
- logger.error(f"Google Calendar API Error: {response.status_code} - {response.text}")
539
- return f"Error retrieving event instances: {response.status_code} - {response.text}"
540
- except NotAuthorizedError as e:
541
- logger.warning(f"Google Calendar authorization required: {e.message}")
542
- return e.message
543
- except Exception as e:
544
- logger.exception(f"Error retrieving event instances: {type(e).__name__} - {str(e)}")
545
- return f"Error retrieving event instances: {type(e).__name__} - {str(e)}"
403
+ url = f"{self.base_api_url}/events/{event_id}/instances"
404
+
405
+ # Build query parameters
406
+ params = {
407
+ "maxResults": max_results,
408
+ "showDeleted": str(show_deleted).lower()
409
+ }
410
+
411
+ # Add optional parameters if provided
412
+ if time_min:
413
+ params["timeMin"] = time_min
414
+
415
+ if time_max:
416
+ params["timeMax"] = time_max
417
+
418
+ if time_zone:
419
+ params["timeZone"] = time_zone
420
+
421
+ if page_token:
422
+ params["pageToken"] = page_token
423
+
424
+ logger.info(f"Retrieving instances of recurring event with ID: {event_id}")
425
+
426
+ response = self._get(url, params=params)
427
+ response.raise_for_status()
428
+
429
+ data = response.json()
430
+ instances = data.get("items", [])
431
+
432
+ if not instances:
433
+ return f"No instances found for recurring event with ID: {event_id}"
434
+
435
+ # Extract event summary from the first instance
436
+ parent_summary = instances[0].get("summary", "Untitled recurring event")
437
+
438
+ result = f"Instances of recurring event: {parent_summary}\n\n"
439
+
440
+ # Process and format each instance
441
+ for i, instance in enumerate(instances, 1):
442
+ # Get instance ID and status
443
+ instance_id = instance.get("id", "No ID")
444
+ status = instance.get("status", "confirmed")
445
+
446
+ # Format status for display
447
+ status_display = ""
448
+ if status == "cancelled":
449
+ status_display = " [CANCELLED]"
450
+ elif status == "tentative":
451
+ status_display = " [TENTATIVE]"
452
+
453
+ # Get instance time
454
+ start = instance.get("start", {})
455
+ original_start_time = instance.get("originalStartTime", {})
456
+
457
+ # Determine if this is a modified instance
458
+ is_modified = original_start_time and "dateTime" in original_start_time
459
+ modified_indicator = " [MODIFIED]" if is_modified else ""
460
+
461
+ # Get the time information
462
+ start_time = start.get("dateTime", start.get("date", "Unknown"))
463
+
464
+ # Format the time using the helper function
465
+ formatted_time = self._format_datetime(start_time)
466
+
467
+ # Format the instance information
468
+ result += f"{i}. {formatted_time}{status_display}{modified_indicator}\n"
469
+ result += f" Instance ID: {instance_id}\n"
470
+
471
+ # Show original start time if modified
472
+ if is_modified:
473
+ orig_time = original_start_time.get("dateTime", original_start_time.get("date", "Unknown"))
474
+ orig_formatted = self._format_datetime(orig_time)
475
+ result += f" Original time: {orig_formatted}\n"
476
+
477
+ # Add a separator between instances
478
+ if i < len(instances):
479
+ result += "\n"
480
+
481
+ # Add pagination info if available
482
+ if "nextPageToken" in data:
483
+ next_token = data.get("nextPageToken")
484
+ result += f"\nMore instances available. Use page_token='{next_token}' to see more."
485
+
486
+ return result
546
487
 
547
488
  def list_tools(self):
548
489
  return [self.get_event, self.get_today_events, self.list_events, self.quick_add_event, self.get_event_instances]