package workflow import ( "context" "encoding/json" "fmt" "strings" "github.com/muyue/muyue/internal/config" "github.com/muyue/muyue/internal/orchestrator" ) type Planner struct { orchestrator *orchestrator.Orchestrator } func NewPlanner(cfg *config.MuyueConfig) (*Planner, error) { orb, err := orchestrator.New(cfg) if err != nil { return nil, err } orb.SetSystemPrompt(plannerSystemPrompt) return &Planner{orchestrator: orb}, nil } func (p *Planner) GeneratePlan(ctx context.Context, goal string) ([]Step, error) { prompt := buildPlanPrompt(goal) messages := []orchestrator.Message{ {Role: "user", Content: prompt}, } resp, err := p.orchestrator.SendWithTools(messages) if err != nil { return nil, err } if len(resp.Choices) == 0 || resp.Choices[0].Message.Content == "" { return nil, fmt.Errorf("no plan generated") } content := resp.Choices[0].Message.Content plan, err := parsePlanResponse(content) if err != nil { return nil, err } return plan, nil } func buildPlanPrompt(goal string) string { return fmt.Sprintf(`Tu es un planificateur de workflows pour Muyue. L'utilisateur veut accomplir la tâche suivante: "%s" Analyse cette tâche et génère un plan d'exécution en une série d'étapes. Chaque étape est un appel d'outil. Les outils disponibles sont: - terminal: Exécuter une commande shell - read_file: Lire un fichier - list_files: Lister les fichiers d'un répertoire - search_files: Rechercher des fichiers par pattern - grep_content: Rechercher du texte dans des fichiers - get_config: Lire la configuration Muyue - set_provider: Configurer un provider AI - manage_ssh: Gérer les connexions SSH - web_fetch: Récupérer le contenu d'une URL Réponds UNIQUEMENT avec un JSON valide représentant un tableau d'étapes, sans texte avant ou après: [ {"name": "Nom de l'étape", "tool": "terminal", "args": {"command": "ls -la"}}, {"name": "Lire le fichier config", "tool": "read_file", "args": {"path": "~/.muyue/config.json"}} ] Règles: - Chaque étape doit avoir: name, tool, args - Les args varient selon le tool (voir les définitions) - Sois précis dans les commandes - Sépare en étapes logiques - Ne génère pas plus de 10 étapes`, goal) } func parsePlanResponse(content string) ([]Step, error) { content = strings.TrimSpace(content) var jsonStr string if strings.HasPrefix(content, "```json") { lines := strings.Split(content, "\n") var jsonLines []string for _, line := range lines[1:] { if strings.HasPrefix(line, "```") { break } jsonLines = append(jsonLines, line) } jsonStr = strings.Join(jsonLines, "\n") } else if strings.HasPrefix(content, "```") { lines := strings.Split(content, "\n") var jsonLines []string for _, line := range lines[1:] { if strings.HasPrefix(line, "```") { break } jsonLines = append(jsonLines, line) } jsonStr = strings.Join(jsonLines, "\n") } else { jsonStr = content } var rawSteps []map[string]interface{} if err := json.Unmarshal([]byte(jsonStr), &rawSteps); err != nil { return nil, fmt.Errorf("failed to parse plan JSON: %v\nContent: %s", err, content) } steps := make([]Step, 0, len(rawSteps)) for i, raw := range rawSteps { step := Step{ ID: fmt.Sprintf("step-%d", i), Status: StatusPending, } if name, ok := raw["name"].(string); ok { step.Name = name } else { step.Name = fmt.Sprintf("Step %d", i+1) } if tool, ok := raw["tool"].(string); ok { step.Tool = tool step.Type = TypeToolCall } if args, ok := raw["args"].(map[string]interface{}); ok { argsJSON, err := json.Marshal(args) if err == nil { step.Args = argsJSON } } if tool, ok := raw["type"].(string); ok { switch tool { case "approval": step.Type = TypeApproval case "condition": step.Type = TypeCondition if cond, ok := raw["condition"].(string); ok { step.Condition = cond } default: step.Type = TypeToolCall } } steps = append(steps, step) } return steps, nil } const plannerSystemPrompt = `Tu es un assistant de planification de workflows pour Muyue. Tu génères des plans d'exécution sous forme de JSON. Chaque plan est une séquence d'étapes (steps) représentant des appels d'outils. Pour générer un plan: 1. Comprends l'objectif de l'utilisateur 2. Identifie les outils nécessaires 3. Décompose en étapes logiques 4. Spécifie les paramètres de chaque outil Réponds toujours en JSON valide, sans texte additionnel.` var _ = plannerSystemPrompt