| """ |
| Calendar Tool - Google Calendar Integration |
| |
| Provides Google Calendar operations with governance integration. |
| |
| Supports: |
| - Checking schedule conflicts |
| - Getting upcoming events |
| - Creating and updating calendar events |
| - Deleting events |
| |
| Governance: |
| - Read actions (get_events, check_conflicts): INTERN+ maturity |
| - Write actions (create_event, update_event, delete_event): SUPERVISED+ maturity |
| - STUDENT agents blocked from all calendar operations |
| """ |
|
|
| import asyncio |
| from typing import Any, Dict, List, Optional, Tuple |
| from datetime import datetime, timedelta |
|
|
| from core.database import get_db_session |
| from core.governance_cache import GovernanceCache |
| from core.models import AgentRegistry |
| from integrations.google_calendar_service import google_calendar_service |
| from core.structured_logger import get_logger |
|
|
| logger = get_logger(__name__) |
|
|
| |
| _governance_cache = GovernanceCache() |
|
|
|
|
| class CalendarTool: |
| """ |
| Calendar tool for AI agents. |
| |
| Provides access to Google Calendar for scheduling and event management: |
| - Get upcoming events |
| - Check for scheduling conflicts |
| - Create new events and meetings |
| - Update or reschedule existing events |
| - Cancel/delete events |
| |
| **Governance**: |
| - Read operations (get_events, check_conflicts): INTERN+ maturity |
| - Write operations (create_event, update_event, delete_event): SUPERVISED+ maturity |
| - STUDENT agents blocked from all operations |
| |
| Examples: |
| - "What is my schedule for today?" |
| - "Check if I am free tomorrow at 2 PM" |
| - "Schedule a meeting with the design team on Friday at 10 AM" |
| - "Reschedule my 1 PM meeting to 3 PM" |
| """ |
|
|
| def __init__(self): |
| """Initialize Calendar tool.""" |
| |
| self.governance_cache = GovernanceCache() |
| |
| |
| try: |
| |
| authenticated = google_calendar_service.authenticate() |
| logger.info("CalendarTool initialized", tool_available=True, authenticated=authenticated) |
| except Exception as e: |
| logger.warning(f"CalendarTool initialized but auth failed: {e}") |
|
|
| async def run( |
| self, |
| action: str, |
| agent_id: Optional[str] = None, |
| maturity_level: Optional[str] = None, |
| user_id: Optional[str] = None, |
| **kwargs |
| ) -> Dict[str, Any]: |
| """ |
| Execute Calendar operation with governance enforcement. |
| |
| Args: |
| action: Operation to perform (get_events, create_event, etc.) |
| agent_id: Agent identifier for governance check |
| user_id: User ID for token lookup |
| **kwargs: Action-specific parameters |
| |
| Returns: |
| Dict with operation results |
| |
| Raises: |
| PermissionError: If maturity level too low |
| """ |
| |
| read_actions = { |
| "get_events", |
| "check_conflicts" |
| } |
|
|
| write_actions = { |
| "create_event", |
| "update_event", |
| "delete_event" |
| } |
|
|
| |
| if action in read_actions: |
| required_maturity = "INTERN" |
| elif action in write_actions: |
| required_maturity = "SUPERVISED" |
| else: |
| return { |
| "success": False, |
| "error": f"Unknown action: {action}", |
| "available_actions": sorted(read_actions | write_actions) |
| } |
|
|
| |
| allowed, reason = await self._check_calendar_permission( |
| agent_id=agent_id, |
| user_id=user_id or "default", |
| action=action, |
| required_maturity=required_maturity |
| ) |
|
|
| if not allowed: |
| logger.warning( |
| "Calendar permission denied", |
| action=action, |
| agent_id=agent_id, |
| reason=reason |
| ) |
| raise PermissionError(reason) |
|
|
| |
| try: |
| result = await self._execute_action( |
| action=action, |
| user_id=user_id or "default", |
| **kwargs |
| ) |
|
|
| logger.info( |
| "Calendar action completed", |
| action=action, |
| agent_id=agent_id, |
| success=result.get("success", False) |
| ) |
|
|
| return result |
|
|
| except PermissionError: |
| raise |
| except Exception as e: |
| logger.error( |
| "Calendar action failed", |
| action=action, |
| error=str(e), |
| kwargs=kwargs |
| ) |
| return { |
| "success": False, |
| "error": str(e), |
| "action": action |
| } |
|
|
| async def _check_calendar_permission( |
| self, |
| agent_id: Optional[str], |
| user_id: str, |
| action: str, |
| required_maturity: str |
| ) -> Tuple[bool, Optional[str]]: |
| """ |
| Check if agent has permission for Calendar operation. |
| """ |
| |
| if not agent_id: |
| return True, None |
|
|
| |
| cache_key = f"calendar_{action}" |
| cached = _governance_cache.get(agent_id, cache_key) |
| if cached: |
| return cached.get("allowed", False), cached.get("reason") |
|
|
| |
| try: |
| with get_db_session() as db: |
| agent = db.query(AgentRegistry).filter( |
| AgentRegistry.id == agent_id |
| ).first() |
|
|
| if not agent: |
| return False, f"Agent '{agent_id}' not found" |
|
|
| |
| maturity = agent.maturity_level |
| maturity_order = ["STUDENT", "INTERN", "SUPERVISED", "AUTONOMOUS"] |
|
|
| try: |
| current_level = maturity_order.index(maturity) |
| required_level = maturity_order.index(required_maturity) |
| except ValueError: |
| return False, f"Invalid maturity level: {maturity}" |
|
|
| allowed = current_level >= required_level |
| reason = None |
|
|
| if not allowed: |
| reason = ( |
| f"Calendar {action} requires {required_maturity}+ maturity " |
| f"(agent is {maturity})" |
| ) |
|
|
| |
| _governance_cache.set(agent_id, cache_key, { |
| "allowed": allowed, |
| "reason": reason, |
| "maturity": maturity |
| }) |
|
|
| return allowed, reason |
|
|
| except Exception as e: |
| logger.error("Permission check failed", error=str(e)) |
| return False, f"Permission check failed: {str(e)}" |
|
|
| async def _execute_action( |
| self, |
| action: str, |
| user_id: str, |
| **kwargs |
| ) -> Dict[str, Any]: |
| """ |
| Execute specific Calendar action. |
| """ |
| |
| if not google_calendar_service.authenticate(): |
| return { |
| "success": False, |
| "error": "Google Calendar is not authenticated. Please connect your Google account in Settings." |
| } |
|
|
| |
| if action == "get_events": |
| time_min_str = kwargs.get("time_min") |
| time_max_str = kwargs.get("time_max") |
| max_results = kwargs.get("max_results", 100) |
| |
| time_min = datetime.fromisoformat(time_min_str) if time_min_str else datetime.utcnow() |
| time_max = datetime.fromisoformat(time_max_str) if time_max_str else (time_min + timedelta(days=7)) |
|
|
| events = await google_calendar_service.get_events( |
| time_min=time_min, |
| time_max=time_max, |
| max_results=max_results |
| ) |
| |
| return { |
| "success": True, |
| "action": "get_events", |
| "count": len(events), |
| "events": events |
| } |
|
|
| elif action == "check_conflicts": |
| start_time_str = kwargs.get("start_time") |
| end_time_str = kwargs.get("end_time") |
| |
| if not start_time_str or not end_time_str: |
| return {"success": False, "error": "start_time and end_time parameters required"} |
| |
| start_time = datetime.fromisoformat(start_time_str) |
| end_time = datetime.fromisoformat(end_time_str) |
|
|
| result = await google_calendar_service.check_conflicts( |
| start_time=start_time, |
| end_time=end_time |
| ) |
| result["action"] = "check_conflicts" |
| return result |
|
|
| elif action == "create_event": |
| title = kwargs.get("title") |
| start_time = kwargs.get("start_time") |
| end_time = kwargs.get("end_time") |
| |
| if not title or not start_time or not end_time: |
| return {"success": False, "error": "title, start_time, and end_time parameters required"} |
|
|
| event_data = { |
| "title": title, |
| "description": kwargs.get("description", ""), |
| "start_time": start_time, |
| "end_time": end_time, |
| "location": kwargs.get("location", ""), |
| "attendees": kwargs.get("attendees", []) |
| } |
|
|
| event = await google_calendar_service.create_event(event_data) |
| |
| if event: |
| return { |
| "success": True, |
| "action": "create_event", |
| "event": event |
| } |
| else: |
| return { |
| "success": False, |
| "error": "Failed to create event" |
| } |
|
|
| elif action == "update_event": |
| event_id = kwargs.get("event_id") |
| updates = kwargs.get("updates") |
| |
| if not event_id or not updates: |
| return {"success": False, "error": "event_id and updates object required"} |
|
|
| event = await google_calendar_service.update_event( |
| event_id=event_id, |
| updates=updates |
| ) |
| |
| if event: |
| return { |
| "success": True, |
| "action": "update_event", |
| "event": event |
| } |
| else: |
| return { |
| "success": False, |
| "error": f"Failed to update event {event_id}" |
| } |
|
|
| elif action == "delete_event": |
| event_id = kwargs.get("event_id") |
| |
| if not event_id: |
| return {"success": False, "error": "event_id required"} |
|
|
| success = await google_calendar_service.delete_event(event_id=event_id) |
| |
| return { |
| "success": success, |
| "action": "delete_event", |
| "event_id": event_id |
| } |
|
|
| else: |
| return { |
| "success": False, |
| "error": f"Unknown action: {action}", |
| "available_actions": ["get_events", "check_conflicts", "create_event", "update_event", "delete_event"] |
| } |
|
|
|
|
| |
|
|
| def register_calendar_tool(tool_registry=None): |
| """ |
| Register CalendarTool with tool registry. |
| """ |
| from tools.registry import ToolRegistry, get_tool_registry |
|
|
| if tool_registry is None: |
| tool_registry = get_tool_registry() |
|
|
| calendar_tool = CalendarTool() |
|
|
| tool_registry.register( |
| name="calendar_tool", |
| function=calendar_tool.run, |
| version="1.0.0", |
| description="Google Calendar tool for AI agents. Provides event creation, conflict checking, and schedule retrieval.", |
| category="productivity", |
| complexity=3, |
| maturity_required="INTERN", |
| dependencies=["google-api-python-client", "google-auth-oauthlib"], |
| tags=["calendar", "schedule", "meeting", "events", "time", "google"] |
| ) |
|
|
| logger.info("CalendarTool registered with ToolRegistry") |
|
|
| return calendar_tool |
|
|