Files
2026-02-09 00:33:40 +01:00

269 lines
7.5 KiB
Go

package action
import (
"context"
"fmt"
"strings"
"time"
"github.com/mudler/LocalAGI/core/scheduler"
"github.com/mudler/LocalAGI/core/types"
"github.com/sashabaranov/go-openai/jsonschema"
)
const (
RecurringReminderActionName = "set_recurring_task"
OneTimeReminderActionName = "set_onetime_task"
ListRemindersName = "list_tasks"
RemoveReminderName = "remove_task"
)
func NewRecurringReminder() *RecurringReminderAction {
return &RecurringReminderAction{}
}
func NewOneTimeReminder() *OneTimeReminderAction {
return &OneTimeReminderAction{}
}
func NewListReminders() *ListRemindersAction {
return &ListRemindersAction{}
}
func NewRemoveReminder() *RemoveReminderAction {
return &RemoveReminderAction{}
}
type RecurringReminderAction struct{}
type OneTimeReminderAction struct{}
type ListRemindersAction struct{}
type RemoveReminderAction struct{}
type RemoveReminderParams struct {
Index int `json:"index"`
}
func (a *RecurringReminderAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
result := types.RecurringReminderParams{}
err := params.Unmarshal(&result)
if err != nil {
return types.ActionResult{}, err
}
task, err := scheduler.NewTask(
sharedState.AgentName,
result.Message,
scheduler.ScheduleTypeCron,
result.CronExpr,
)
if err != nil {
return types.ActionResult{}, err
}
task.Metadata["reminder_type"] = "user_created"
err = sharedState.Scheduler.CreateTask(task)
if err != nil {
return types.ActionResult{}, err
}
return types.ActionResult{
Result: fmt.Sprintf("Recurring reminder set successfully (ID: %s). Next run: %s", task.ID, task.NextRun.Format(time.RFC3339)),
Metadata: map[string]interface{}{
"task_id": task.ID,
"message": result.Message,
"next_run": task.NextRun,
},
}, nil
}
func (a *OneTimeReminderAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
result := types.OneTimeReminderParams{}
err := params.Unmarshal(&result)
if err != nil {
return types.ActionResult{}, err
}
// Validate the delay parses correctly before creating the task
_, err = scheduler.ParseDuration(result.Delay)
if err != nil {
return types.ActionResult{}, fmt.Errorf("invalid delay format, expected a duration like '30m', '2h', '1d', '1d12h': %w", err)
}
task, err := scheduler.NewTask(
sharedState.AgentName,
result.Message,
scheduler.ScheduleTypeOnce,
result.Delay,
)
if err != nil {
return types.ActionResult{}, err
}
task.Metadata["reminder_type"] = "user_created"
err = sharedState.Scheduler.CreateTask(task)
if err != nil {
return types.ActionResult{}, err
}
return types.ActionResult{
Result: fmt.Sprintf("One-time reminder set in %s (at %s, ID: %s)", result.Delay, task.NextRun.Format(time.RFC3339), task.ID),
Metadata: map[string]interface{}{
"task_id": task.ID,
"message": result.Message,
"next_run": task.NextRun,
},
}, nil
}
func (a *ListRemindersAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
tasks, err := sharedState.Scheduler.GetAllTasks()
if err != nil {
return types.ActionResult{}, err
}
if len(tasks) == 0 {
return types.ActionResult{
Result: "No reminders set",
}, nil
}
var result strings.Builder
result.WriteString("Current reminders:\n")
for i, task := range tasks {
status := "one-time"
if task.ScheduleType == scheduler.ScheduleTypeCron || task.ScheduleType == scheduler.ScheduleTypeInterval {
status = "recurring"
}
result.WriteString(fmt.Sprintf("%d. %s (Next run: %s, Status: %s, ID: %s)\n",
i+1,
task.Prompt,
task.NextRun.Format(time.RFC3339),
status,
task.ID))
}
return types.ActionResult{
Result: result.String(),
Metadata: map[string]interface{}{
"tasks": tasks,
},
}, 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
}
tasks, err := sharedState.Scheduler.GetAllTasks()
if err != nil {
return types.ActionResult{}, err
}
if len(tasks) == 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(tasks) {
return types.ActionResult{}, fmt.Errorf("invalid reminder index: %d", removeParams.Index)
}
task := tasks[index]
err = sharedState.Scheduler.DeleteTask(task.ID)
if err != nil {
return types.ActionResult{}, err
}
return types.ActionResult{
Result: fmt.Sprintf("Removed reminder: %s", task.Prompt),
Metadata: map[string]interface{}{
"removed_task_id": task.ID,
},
}, nil
}
func (a *RecurringReminderAction) Plannable() bool {
return true
}
func (a *OneTimeReminderAction) Plannable() bool {
return true
}
func (a *ListRemindersAction) Plannable() bool {
return true
}
func (a *RemoveReminderAction) Plannable() bool {
return true
}
func (a *RecurringReminderAction) Definition() types.ActionDefinition {
return types.ActionDefinition{
Name: RecurringReminderActionName,
Description: "Set a recurring reminder for the agent to wake up and perform a task on a cron schedule. The reminder will keep repeating. 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: 'minute hour day month weekday'",
},
},
Required: []string{"message", "cron_expr"},
}
}
func (a *OneTimeReminderAction) Definition() types.ActionDefinition {
return types.ActionDefinition{
Name: OneTimeReminderActionName,
Description: "Set a one-time reminder for the agent to wake up and perform a task after a delay. The reminder triggers only once and is then automatically removed. Use this when asked to do something 'in X minutes/hours/days'. Examples: '30m' (30 minutes), '2h' (2 hours), '1d' (1 day), '1d12h' (1.5 days), '2h30m' (2.5 hours)",
Properties: map[string]jsonschema.Definition{
"message": {
Type: jsonschema.String,
Description: "The message or task to be reminded about",
},
"delay": {
Type: jsonschema.String,
Description: "How long to wait before triggering. Use Go duration format: '30m' (30 minutes), '2h' (2 hours), '1d' (1 day), '1d12h' (1.5 days), '2h30m' (2.5 hours)",
},
},
Required: []string{"message", "delay"},
}
}
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"},
}
}