package action import ( "context" "fmt" "strings" "time" "github.com/mudler/LocalAGI/core/types" "github.com/robfig/cron/v3" "github.com/sashabaranov/go-openai/jsonschema" ) const ( ReminderActionName = "set_reminder" ListRemindersName = "list_reminders" RemoveReminderName = "remove_reminder" ) func NewReminder() *ReminderAction { return &ReminderAction{} } func NewListReminders() *ListRemindersAction { return &ListRemindersAction{} } func NewRemoveReminder() *RemoveReminderAction { return &RemoveReminderAction{} } type ReminderAction struct{} type ListRemindersAction struct{} type RemoveReminderAction struct{} type RemoveReminderParams struct { Index int `json:"index"` } func (a *ReminderAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) { result := types.ReminderActionResponse{} err := params.Unmarshal(&result) if err != nil { return types.ActionResult{}, err } // Validate the cron expression parser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) _, err = parser.Parse(result.CronExpr) if err != nil { return types.ActionResult{}, err } // Calculate next run time now := time.Now() schedule, _ := parser.Parse(result.CronExpr) // We can ignore the error since we validated above nextRun := schedule.Next(now) // Set the reminder details result.LastRun = now result.NextRun = nextRun // IsRecurring is set by the user through the action parameters // Store the reminder in the shared state if sharedState.Reminders == nil { sharedState.Reminders = make([]types.ReminderActionResponse, 0) } sharedState.Reminders = append(sharedState.Reminders, result) return types.ActionResult{ Result: "Reminder set successfully", Metadata: map[string]interface{}{ "reminder": result, }, }, nil } func (a *ListRemindersAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) { if sharedState.Reminders == nil || len(sharedState.Reminders) == 0 { return types.ActionResult{ Result: "No reminders set", }, nil } var result strings.Builder result.WriteString("Current reminders:\n") for i, reminder := range sharedState.Reminders { status := "one-time" if reminder.IsRecurring { status = "recurring" } result.WriteString(fmt.Sprintf("%d. %s (Next run: %s, Status: %s)\n", i+1, reminder.Message, reminder.NextRun.Format(time.RFC3339), status)) } return types.ActionResult{ Result: result.String(), Metadata: map[string]interface{}{ "reminders": sharedState.Reminders, }, }, nil } func (a *RemoveReminderAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) { var removeParams RemoveReminderParams err := params.Unmarshal(&removeParams) if err != nil { return types.ActionResult{}, err } if sharedState.Reminders == nil || len(sharedState.Reminders) == 0 { return types.ActionResult{ Result: "No reminders to remove", }, nil } // Convert from 1-based index to 0-based index := removeParams.Index - 1 if index < 0 || index >= len(sharedState.Reminders) { return types.ActionResult{}, fmt.Errorf("invalid reminder index: %d", removeParams.Index) } // Remove the reminder removed := sharedState.Reminders[index] sharedState.Reminders = append(sharedState.Reminders[:index], sharedState.Reminders[index+1:]...) return types.ActionResult{ Result: fmt.Sprintf("Removed reminder: %s", removed.Message), Metadata: map[string]interface{}{ "removed_reminder": removed, }, }, nil } func (a *ReminderAction) Plannable() bool { return true } func (a *ListRemindersAction) Plannable() bool { return true } func (a *RemoveReminderAction) Plannable() bool { return true } func (a *ReminderAction) Definition() types.ActionDefinition { return types.ActionDefinition{ Name: ReminderActionName, Description: "Set a reminder for the agent to wake up and perform a task based on a cron schedule. Examples: '0 0 * * *' (daily at midnight), '0 */2 * * *' (every 2 hours), '0 0 * * 1' (every Monday at midnight)", Properties: map[string]jsonschema.Definition{ "message": { Type: jsonschema.String, Description: "The message or task to be reminded about", }, "cron_expr": { Type: jsonschema.String, Description: "Cron expression for scheduling (e.g. '0 0 * * *' for daily at midnight). Format: 'second minute hour day month weekday'", }, "is_recurring": { Type: jsonschema.Boolean, Description: "Whether this reminder should repeat according to the cron schedule (true) or trigger only once (false)", }, }, Required: []string{"message", "cron_expr", "is_recurring"}, } } func (a *ListRemindersAction) Definition() types.ActionDefinition { return types.ActionDefinition{ Name: ListRemindersName, Description: "List all currently set reminders with their next scheduled run times", Properties: map[string]jsonschema.Definition{}, Required: []string{}, } } func (a *RemoveReminderAction) Definition() types.ActionDefinition { return types.ActionDefinition{ Name: RemoveReminderName, Description: "Remove a reminder by its index number (use list_reminders to see the index)", Properties: map[string]jsonschema.Definition{ "index": { Type: jsonschema.Integer, Description: "The index number of the reminder to remove (1-based)", }, }, Required: []string{"index"}, } }