Add EventBridge scheduling: schedule_reminder, list_reminders, cancel_reminder
This commit is contained in:
141
agentclaw/app/agent_claw_main/tools/scheduler.py
Normal file
141
agentclaw/app/agent_claw_main/tools/scheduler.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""EventBridge scheduling tools: schedule_reminder, list_reminders, cancel_reminder."""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import boto3
|
||||
from strands import tool
|
||||
|
||||
# Injected by main.py before each invocation
|
||||
_current_actor_id: str = ''
|
||||
_current_chat_id: str = ''
|
||||
|
||||
SCHEDULER_LAMBDA_ARN = os.environ.get('SCHEDULER_LAMBDA_ARN', '')
|
||||
ACCOUNT_ID = os.environ.get('AWS_ACCOUNT_ID', '')
|
||||
REGION = 'us-east-1'
|
||||
|
||||
|
||||
def _eb():
|
||||
return boto3.client('events', region_name=REGION)
|
||||
|
||||
|
||||
def _rule_prefix() -> str:
|
||||
safe = re.sub(r'[^a-zA-Z0-9_-]', '-', _current_actor_id)
|
||||
return f'agent-claw-reminder-{safe}-'
|
||||
|
||||
|
||||
@tool
|
||||
def schedule_reminder(message: str, when_utc: str) -> str:
|
||||
"""Schedule a one-time reminder to be sent via Telegram at a specific UTC time.
|
||||
|
||||
Args:
|
||||
message: The reminder text to send.
|
||||
when_utc: ISO 8601 UTC datetime, e.g. '2026-05-09T09:00:00' (no timezone suffix).
|
||||
"""
|
||||
if not SCHEDULER_LAMBDA_ARN:
|
||||
return 'SCHEDULER_LAMBDA_ARN not configured.'
|
||||
if not _current_chat_id:
|
||||
return 'chat_id not available.'
|
||||
|
||||
# Convert ISO datetime to EventBridge cron: cron(min hour day month ? year)
|
||||
try:
|
||||
from datetime import datetime
|
||||
dt = datetime.fromisoformat(when_utc.rstrip('Z'))
|
||||
cron_expr = f'cron({dt.minute} {dt.hour} {dt.day} {dt.month} ? {dt.year})'
|
||||
except ValueError as e:
|
||||
return f'Invalid when_utc format: {e}'
|
||||
|
||||
import time
|
||||
rule_name = f'{_rule_prefix()}{int(time.time())}'
|
||||
|
||||
eb = _eb()
|
||||
eb.put_rule(
|
||||
Name=rule_name,
|
||||
ScheduleExpression=cron_expr,
|
||||
State='ENABLED',
|
||||
)
|
||||
|
||||
# Grant EventBridge permission to invoke the Lambda
|
||||
lm = boto3.client('lambda', region_name=REGION)
|
||||
try:
|
||||
lm.add_permission(
|
||||
FunctionName=SCHEDULER_LAMBDA_ARN,
|
||||
StatementId=rule_name,
|
||||
Action='lambda:InvokeFunction',
|
||||
Principal='events.amazonaws.com',
|
||||
SourceArn=f'arn:aws:events:{REGION}:{_account_id()}:rule/{rule_name}',
|
||||
)
|
||||
except lm.exceptions.ResourceConflictException:
|
||||
pass
|
||||
|
||||
eb.put_targets(
|
||||
Rule=rule_name,
|
||||
Targets=[{
|
||||
'Id': 'scheduler',
|
||||
'Arn': SCHEDULER_LAMBDA_ARN,
|
||||
'Input': json.dumps({
|
||||
'chat_id': _current_chat_id,
|
||||
'message': message,
|
||||
'rule_name': rule_name,
|
||||
}),
|
||||
}],
|
||||
)
|
||||
|
||||
return f'Reminder scheduled: "{message}" at {when_utc} UTC (rule: {rule_name})'
|
||||
|
||||
|
||||
@tool
|
||||
def list_reminders() -> str:
|
||||
"""List all pending reminders for the current user."""
|
||||
eb = _eb()
|
||||
prefix = _rule_prefix()
|
||||
rules = []
|
||||
kwargs: dict = {'NamePrefix': prefix}
|
||||
while True:
|
||||
resp = eb.list_rules(**kwargs)
|
||||
rules.extend(resp.get('Rules', []))
|
||||
token = resp.get('NextToken')
|
||||
if not token:
|
||||
break
|
||||
kwargs['NextToken'] = token
|
||||
|
||||
if not rules:
|
||||
return 'No pending reminders.'
|
||||
|
||||
lines = []
|
||||
for r in rules:
|
||||
lines.append(f"- {r['Name']}: {r.get('ScheduleExpression', '')} [{r.get('State', '')}]")
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
@tool
|
||||
def cancel_reminder(rule_name: str) -> str:
|
||||
"""Cancel a scheduled reminder by its rule name.
|
||||
|
||||
Args:
|
||||
rule_name: The EventBridge rule name (from list_reminders).
|
||||
"""
|
||||
prefix = _rule_prefix()
|
||||
if not rule_name.startswith(prefix):
|
||||
return f'Rule "{rule_name}" does not belong to your account.'
|
||||
|
||||
eb = _eb()
|
||||
try:
|
||||
eb.remove_targets(Rule=rule_name, Ids=['scheduler'])
|
||||
eb.delete_rule(Name=rule_name)
|
||||
except eb.exceptions.ResourceNotFoundException:
|
||||
return f'Rule "{rule_name}" not found.'
|
||||
|
||||
# Remove Lambda permission
|
||||
lm = boto3.client('lambda', region_name=REGION)
|
||||
try:
|
||||
lm.remove_permission(FunctionName=SCHEDULER_LAMBDA_ARN, StatementId=rule_name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return f'Reminder "{rule_name}" cancelled.'
|
||||
|
||||
|
||||
def _account_id() -> str:
|
||||
if ACCOUNT_ID:
|
||||
return ACCOUNT_ID
|
||||
return boto3.client('sts', region_name=REGION).get_caller_identity()['Account']
|
||||
Reference in New Issue
Block a user