""" Enhanced Timer Extension Set and track timers/countdowns with proactive notifications Now with better state management and orchestrator integration """ from base_extension import BaseExtension from google.genai import types from typing import Dict, Any, List, Optional import datetime import uuid class TimerExtension(BaseExtension): @property def name(self) -> str: return "timer" @property def display_name(self) -> str: return "Timer & Reminders" @property def description(self) -> str: return "Set timers and reminders for time-sensitive tasks with automatic notifications" @property def icon(self) -> str: return "⏰" @property def version(self) -> str: return "2.0.0" def get_system_context(self) -> str: return """ You have access to a Timer system for tracking time-sensitive activities. You can: - Set timers with a name and duration (in minutes, hours, or days) - List active timers and check elapsed time - Cancel timers - Calculate remaining time - Get automatic notifications when timers complete When users mention time-sensitive activities (fermentation stages, cooking, steeping, reminders, etc.), offer to set timers. Be proactive about time management. IMPORTANT: Timers persist across conversations! When users ask about "the timer" or "my timers", they're referring to timers created earlier. Always use list_timers to check existing timers before assuming none exist. """ def _get_default_state(self) -> Dict[str, Any]: return { "timers": [], "total_created": 0, "total_completed": 0, "created_at": datetime.datetime.now().isoformat(), "last_updated": datetime.datetime.now().isoformat() } def get_state_summary(self, user_id: str) -> Optional[str]: """Provide state summary for system prompt""" state = self.get_state(user_id) active_timers = [t for t in state.get("timers", []) if t.get("active", False)] if active_timers: return f"{len(active_timers)} active timer(s)" return None def get_metrics(self, user_id: str) -> Dict[str, Any]: """Provide usage metrics""" state = self.get_state(user_id) return { "total_created": state.get("total_created", 0), "total_completed": state.get("total_completed", 0), "currently_active": len([t for t in state.get("timers", []) if t.get("active", False)]) } def get_tools(self) -> List[types.Tool]: set_timer = types.FunctionDeclaration( name="set_timer", description="Set a new timer with a name and duration", parameters={ "type": "object", "properties": { "name": {"type": "string", "description": "Timer name/description"}, "duration_minutes": { "type": "number", "description": "Duration in minutes (can be fractional, e.g., 1440 for 1 day)" } }, "required": ["name", "duration_minutes"] } ) list_timers = types.FunctionDeclaration( name="list_timers", description="List all active timers with elapsed and remaining time. ALWAYS call this when user asks about 'the timer' or 'my timers'.", parameters={"type": "object", "properties": {}} ) check_timer = types.FunctionDeclaration( name="check_timer", description="Check status of a specific timer by ID", parameters={ "type": "object", "properties": { "timer_id": {"type": "string", "description": "Timer ID from list_timers"} }, "required": ["timer_id"] } ) cancel_timer = types.FunctionDeclaration( name="cancel_timer", description="Cancel/delete a timer by ID", parameters={ "type": "object", "properties": { "timer_id": {"type": "string", "description": "Timer ID to cancel"} }, "required": ["timer_id"] } ) return [types.Tool(function_declarations=[ set_timer, list_timers, check_timer, cancel_timer ])] def _execute_tool(self, user_id: str, tool_name: str, args: Dict[str, Any]) -> Any: """Execute tool logic""" state = self.get_state(user_id) if tool_name == "set_timer": start_time = datetime.datetime.now() duration_mins = args["duration_minutes"] end_time = start_time + datetime.timedelta(minutes=duration_mins) timer = { "id": str(uuid.uuid4())[:8], "name": args["name"], "start_time": start_time.isoformat(), "end_time": end_time.isoformat(), "duration_minutes": duration_mins, "active": True, "notified": False } state["timers"].append(timer) state["total_created"] = state.get("total_created", 0) + 1 self.update_state(user_id, state) # Log activity self.log_activity(user_id, "timer_created", { "timer_id": timer["id"], "name": timer["name"], "duration": duration_mins }) # Format duration for display if duration_mins < 60: duration_str = f"{duration_mins} minutes" elif duration_mins < 1440: hours = duration_mins / 60 duration_str = f"{hours:.1f} hours" else: days = duration_mins / 1440 duration_str = f"{days:.1f} days" return { "success": True, "message": f"⏰ Timer set: '{timer['name']}' for {duration_str}", "timer_id": timer["id"], "end_time": end_time.strftime("%Y-%m-%d %H:%M:%S"), "duration_display": duration_str } elif tool_name == "list_timers": now = datetime.datetime.now() active_timers = [] for timer in state["timers"]: if not timer["active"]: continue start = datetime.datetime.fromisoformat(timer["start_time"]) end = datetime.datetime.fromisoformat(timer["end_time"]) elapsed = (now - start).total_seconds() / 60 remaining = (end - now).total_seconds() / 60 active_timers.append({ "id": timer["id"], "name": timer["name"], "elapsed_minutes": round(elapsed, 1), "remaining_minutes": round(remaining, 1), "is_complete": remaining <= 0, "end_time": timer["end_time"], "percentage_complete": min(100, (elapsed / timer["duration_minutes"]) * 100) }) return { "active_timers": active_timers, "count": len(active_timers), "message": f"Found {len(active_timers)} active timer(s)" if active_timers else "No active timers" } elif tool_name == "check_timer": timer_id = args["timer_id"] now = datetime.datetime.now() for timer in state["timers"]: if timer["id"] == timer_id and timer["active"]: start = datetime.datetime.fromisoformat(timer["start_time"]) end = datetime.datetime.fromisoformat(timer["end_time"]) elapsed = (now - start).total_seconds() / 60 remaining = (end - now).total_seconds() / 60 return { "id": timer["id"], "name": timer["name"], "elapsed_minutes": round(elapsed, 1), "remaining_minutes": round(remaining, 1), "is_complete": remaining <= 0, "percentage_complete": min(100, (elapsed / timer["duration_minutes"]) * 100), "end_time": timer["end_time"] } return {"success": False, "error": f"Timer {timer_id} not found"} elif tool_name == "cancel_timer": timer_id = args["timer_id"] for timer in state["timers"]: if timer["id"] == timer_id: timer["active"] = False self.update_state(user_id, state) # Log activity self.log_activity(user_id, "timer_cancelled", { "timer_id": timer_id, "name": timer["name"] }) return { "success": True, "message": f"✅ Cancelled timer: {timer['name']}" } return {"success": False, "error": f"Timer {timer_id} not found"} return {"error": f"Unknown tool: {tool_name}"} def get_proactive_message(self, user_id: str) -> Optional[str]: """Check for completed timers and send notifications""" state = self.get_state(user_id) now = datetime.datetime.now() newly_completed = [] for timer in state.get("timers", []): if timer.get("active") and not timer.get("notified", False): end_time = datetime.datetime.fromisoformat(timer["end_time"]) if now >= end_time: newly_completed.append(timer) timer["notified"] = True if newly_completed: self.update_state(user_id, state) state["total_completed"] = state.get("total_completed", 0) + len(newly_completed) # Create notification message if len(newly_completed) == 1: timer = newly_completed[0] return f"⏰ **Timer Complete!** Your timer '{timer['name']}' has finished!" else: timer_names = ", ".join([f"'{t['name']}'" for t in newly_completed]) return f"⏰ **{len(newly_completed)} Timers Complete!** {timer_names}" return None def on_enable(self, user_id: str) -> str: self.initialize_state(user_id) return "⏰ Timer & Reminders enabled! I can now help you track time-sensitive tasks with automatic notifications." def on_disable(self, user_id: str) -> str: state = self.get_state(user_id) active = len([t for t in state.get("timers", []) if t.get("active", False)]) return f"⏰ Timer & Reminders disabled. {active} active timer(s) will be cleared." def health_check(self, user_id: str) -> Dict[str, Any]: """Check extension health""" state = self.get_state(user_id) issues = [] # Check for stale timers (older than 30 days) now = datetime.datetime.now() stale_count = 0 for timer in state.get("timers", []): if timer.get("active"): end_time = datetime.datetime.fromisoformat(timer["end_time"]) age_days = (now - end_time).days if age_days > 30: stale_count += 1 if stale_count > 0: issues.append(f"{stale_count} stale timer(s) older than 30 days") return { "healthy": len(issues) == 0, "extension": self.name, "version": self.version, "issues": issues if issues else None }