wuhp's picture
Update extensions/timer.py
4d20cf7 verified
"""
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
}