* feat(reminders): add self-ability to set reminders Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(reminders): surface reminders result to the user as new conversations Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Fixups * Subscribe all connectors to agents new messages Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Set reminders in the list * fix(telegram): do not always auth Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Small fixups * Improve UX Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
194 lines
5.5 KiB
Go
194 lines
5.5 KiB
Go
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"},
|
|
}
|
|
}
|