187 lines
5.4 KiB
Go
187 lines
5.4 KiB
Go
package scheduler
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"trankilou.fr/lassistanoque/backend/internal/domain"
|
|
"trankilou.fr/lassistanoque/backend/internal/service/task"
|
|
)
|
|
|
|
// SchedulerTool permet à l'agent de planifier des tâches pour l'utilisateur :
|
|
// exécution d'un prompt selon une spécification cron, avec ou sans répétition.
|
|
type SchedulerTool struct {
|
|
taskService *task.Service
|
|
}
|
|
|
|
func New(taskService *task.Service) *SchedulerTool {
|
|
return &SchedulerTool{taskService: taskService}
|
|
}
|
|
|
|
func (t *SchedulerTool) Name() string {
|
|
return "scheduler"
|
|
}
|
|
|
|
func (t *SchedulerTool) Description() string {
|
|
return "Schedule tasks that run automatically at specified times, with or without repetition. " +
|
|
"Use this tool whenever the user asks to do something later or on a recurring basis " +
|
|
"(e.g. 'every day at 8am', 'every Monday at 9am', 'tomorrow at 6pm', 'every hour'). " +
|
|
"Actions: 'create' (schedule a new task), 'list' (list scheduled tasks), " +
|
|
"'update' (modify a task's title, prompt, cron or repetition), " +
|
|
"'delete' (delete a task by its id)."
|
|
}
|
|
|
|
func (t *SchedulerTool) Params() []domain.ToolParam {
|
|
return []domain.ToolParam{}
|
|
}
|
|
|
|
func (t *SchedulerTool) Definition(ctx context.Context) *domain.ToolDefinition {
|
|
return &domain.ToolDefinition{
|
|
Type: "function",
|
|
Function: &domain.ToolFunction{
|
|
Name: t.Name(),
|
|
Description: t.Description(),
|
|
Parameters: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"action": map[string]any{
|
|
"type": "string",
|
|
"enum": []string{"create", "list", "update", "delete"},
|
|
"description": "The scheduling action to perform.",
|
|
},
|
|
"title": map[string]any{
|
|
"type": "string",
|
|
"description": "Short task title, e.g. 'Météo du matin'. Required for 'create'.",
|
|
},
|
|
"prompt": map[string]any{
|
|
"type": "string",
|
|
"description": "Self-contained prompt to execute at the scheduled time. " +
|
|
"It will run in a fresh conversation with no memory of this one: " +
|
|
"include all needed context. Required for 'create'.",
|
|
},
|
|
"cron": map[string]any{
|
|
"type": "string",
|
|
"description": "Cron spec, 5 fields 'minute hour day-of-month month day-of-week' " +
|
|
"(e.g. '0 8 * * *' every day at 08:00, '0 9 * * 1' every Monday at 09:00, " +
|
|
"'30 18 1 * *' on the 1st at 18:30) or a descriptor like '@daily', '@hourly', '@weekly'. " +
|
|
"Required for 'create'.",
|
|
},
|
|
"repeat": map[string]any{
|
|
"type": "boolean",
|
|
"description": "true: the task repeats on schedule. " +
|
|
"false: it runs only once, at the first time matching the cron spec, then is marked done. " +
|
|
"Default true. Use false for one-shot requests like 'tomorrow at 8am'.",
|
|
},
|
|
"id": map[string]any{
|
|
"type": "string",
|
|
"description": "Task id, as returned by 'list'. Required for 'update' and 'delete'.",
|
|
},
|
|
},
|
|
"required": []string{"action"},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (t *SchedulerTool) Execute(ctx context.Context, input []byte, configuration string) ([]byte, error) {
|
|
var params struct {
|
|
Action string `json:"action"`
|
|
Title string `json:"title"`
|
|
Prompt string `json:"prompt"`
|
|
Cron string `json:"cron"`
|
|
Repeat *bool `json:"repeat"`
|
|
ID string `json:"id"`
|
|
}
|
|
if err := json.Unmarshal(input, ¶ms); err != nil {
|
|
return nil, fmt.Errorf("invalid parameters: %w", err)
|
|
}
|
|
|
|
userID, ok := domain.UserIDFromContext(ctx)
|
|
if !ok {
|
|
return nil, fmt.Errorf("scheduler tool: missing user context")
|
|
}
|
|
teamID, ok := domain.TeamIDFromContext(ctx)
|
|
if !ok {
|
|
return nil, fmt.Errorf("scheduler tool: missing team context")
|
|
}
|
|
modelID, _ := domain.ModelIDFromContext(ctx)
|
|
|
|
switch params.Action {
|
|
case "create":
|
|
repeat := true
|
|
if params.Repeat != nil {
|
|
repeat = *params.Repeat
|
|
}
|
|
taskModel, err := t.taskService.CreateTask(userID, teamID, &domain.Task{
|
|
ModelID: modelID,
|
|
Label: params.Title,
|
|
Prompt: params.Prompt,
|
|
Cron: params.Cron,
|
|
Repeat: repeat,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result, err := json.Marshal(taskModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
|
|
case "list":
|
|
tasks, err := t.taskService.ListTasks(userID, teamID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result, err := json.Marshal(tasks)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
|
|
case "update":
|
|
if params.ID == "" {
|
|
return nil, fmt.Errorf("'id' is required for action 'update'")
|
|
}
|
|
current, err := t.taskService.GetTask(userID, teamID, params.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// mise à jour partielle : seuls les champs fournis changent
|
|
if params.Title != "" {
|
|
current.Label = params.Title
|
|
}
|
|
if params.Prompt != "" {
|
|
current.Prompt = params.Prompt
|
|
}
|
|
if params.Cron != "" {
|
|
current.Cron = params.Cron
|
|
}
|
|
if params.Repeat != nil {
|
|
current.Repeat = *params.Repeat
|
|
}
|
|
updated, err := t.taskService.UpdateTask(userID, teamID, current)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result, err := json.Marshal(updated)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
|
|
case "delete":
|
|
if params.ID == "" {
|
|
return nil, fmt.Errorf("'id' is required for action 'delete'")
|
|
}
|
|
if err := t.taskService.DeleteTask(userID, teamID, params.ID); err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte("task deleted"), nil
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown action %q", params.Action)
|
|
}
|
|
}
|