package tools import ( "context" "fmt" "strings" "sync" "time" "github.com/YspCoder/clawgo/pkg/cron" ) type RemindTool struct { mu sync.RWMutex cs *cron.CronService defaultChannel string defaultChatID string } func NewRemindTool(cs *cron.CronService) *RemindTool { return &RemindTool{cs: cs} } func (t *RemindTool) SetContext(channel, chatID string) { t.mu.Lock() defer t.mu.Unlock() t.defaultChannel = channel t.defaultChatID = chatID } func (t *RemindTool) Name() string { return "remind" } func (t *RemindTool) Description() string { return "Set a reminder for a future time" } func (t *RemindTool) Parameters() map[string]interface{} { return map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "message": map[string]interface{}{ "type": "string", "description": "The reminder message", }, "time_expr": map[string]interface{}{ "type": "string", "description": "When to remind (e.g., '10m', '1h', '2026-02-12 10:00')", }, "channel": map[string]interface{}{ "type": "string", "description": "Optional destination channel override", }, "chat_id": map[string]interface{}{ "type": "string", "description": "Optional destination chat ID override", }, }, "required": []string{"message", "time_expr"}, } } func (t *RemindTool) Execute(ctx context.Context, args map[string]interface{}) (string, error) { if t.cs == nil { return "", fmt.Errorf("cron service not available") } message := MapRawStringArg(args, "message") if strings.TrimSpace(message) == "" { return "", fmt.Errorf("message is required") } timeExpr := MapStringArg(args, "time_expr") if timeExpr == "" { return "", fmt.Errorf("time_expr is required") } channel := MapStringArg(args, "channel") chatID := MapStringArg(args, "chat_id") if channel == "" || chatID == "" { t.mu.RLock() defaultChannel := t.defaultChannel defaultChatID := t.defaultChatID t.mu.RUnlock() if channel == "" { channel = defaultChannel } if chatID == "" { chatID = defaultChatID } } // Try duration first (e.g., "10m", "1h30m") if d, err := time.ParseDuration(timeExpr); err == nil { at := time.Now().Add(d).UnixMilli() schedule := cron.CronSchedule{ Kind: "at", AtMS: &at, } job, err := t.cs.AddJob("Reminder", schedule, message, true, channel, chatID) if err != nil { return "", fmt.Errorf("failed to schedule reminder: %w", err) } return fmt.Sprintf("Reminder set for %s (in %s). Job ID: %s", time.UnixMilli(at).Format(time.RFC1123), d, job.ID), nil } // Try absolute date/time formats formats := []string{ "2006-01-02 15:04", "2006-01-02 15:04:05", "15:04", "15:04:05", } var parsedTime time.Time var parseErr error parsed := false for _, layout := range formats { if t, err := time.ParseInLocation(layout, timeExpr, time.Local); err == nil { parsedTime = t parsed = true // If format was time-only, use today or tomorrow if layout == "15:04" || layout == "15:04:05" { now := time.Now() // Combine today's date with parsed time combined := time.Date(now.Year(), now.Month(), now.Day(), t.Hour(), t.Minute(), t.Second(), 0, time.Local) if combined.Before(now) { // If time passed today, assume tomorrow combined = combined.Add(24 * time.Hour) } parsedTime = combined } break } else { parseErr = err } } if !parsed { return "", fmt.Errorf("could not parse time expression '%s': %v", timeExpr, parseErr) } at := parsedTime.UnixMilli() schedule := cron.CronSchedule{ Kind: "at", AtMS: &at, } job, err := t.cs.AddJob("Reminder", schedule, message, true, channel, chatID) if err != nil { return "", fmt.Errorf("failed to schedule reminder: %w", err) } return fmt.Sprintf("Reminder set for %s. Job ID: %s", parsedTime.Format(time.RFC1123), job.ID), nil }