All files / src/services tools.js

0% Statements 0/45
0% Branches 0/24
0% Functions 0/5
0% Lines 0/35

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116                                                                                                                                                                                                                                       
/**
 * Outils IA — Exécution des outils disponibles pour les agents IA.
 *
 * Commun à tous les providers (Claude, OpenAI, Mistral).
 * Gère : bash_exec, read_file, write_file.
 *
 * @module services/tools
 */
 
const { exec } = require('./ssh');
const { analyzeCommand } = require('./commandAnalyzer');
const { shellEscape } = require('../utils/shell');
const { validatePath } = require('../utils/pathValidator');
 
const SYSTEM_PROMPT = `Tu es IliaCloud Assistant, un agent IA expert en administration système Linux et Docker.
Tu as accès à un serveur distant via SSH.
Tu peux exécuter des commandes bash pour diagnostiquer des problèmes, lire des fichiers et aider l'utilisateur.
 
Règles impératives :
- Avant d'exécuter une commande destructive (rm -rf, docker stop, etc.), annonce-la et attends la confirmation.
- Ne jamais exécuter plus d'une commande destructive à la fois.
- Explique ce que tu fais avant de le faire.
- En cas d'erreur, analyse le message et propose une solution.
- Réponds en français sauf si l'utilisateur écrit dans une autre langue.`;
 
// Définition des outils au format Anthropic (Claude)
const ANTHROPIC_TOOLS = [
  {
    name: 'bash_exec',
    description: 'Exécute une commande bash sur le serveur distant via SSH. Retourne stdout, stderr et le code de sortie.',
    input_schema: {
      type: 'object',
      properties: { command: { type: 'string', description: 'La commande bash à exécuter sur le serveur.' } },
      required: ['command'],
    },
  },
  {
    name: 'read_file',
    description: 'Lit le contenu d\'un fichier sur le serveur distant.',
    input_schema: {
      type: 'object',
      properties: { path: { type: 'string', description: 'Chemin absolu du fichier à lire.' } },
      required: ['path'],
    },
  },
  {
    name: 'write_file',
    description: 'Écrit ou modifie un fichier sur le serveur distant. ATTENTION : nécessite confirmation utilisateur.',
    input_schema: {
      type: 'object',
      properties: {
        path: { type: 'string', description: 'Chemin absolu du fichier à écrire.' },
        content: { type: 'string', description: 'Contenu à écrire dans le fichier.' },
      },
      required: ['path', 'content'],
    },
  },
];
 
// Définition des outils au format OpenAI (compatible Mistral)
const OPENAI_TOOLS = ANTHROPIC_TOOLS.map((t) => ({
  type: 'function',
  function: {
    name: t.name,
    description: t.description,
    parameters: t.input_schema,
  },
}));
 
async function executeBashExec({ userId, serverId, input, onPendingAction }) {
  const analysis = analyzeCommand(input.command);
  if (analysis.level === 'dangerous' && onPendingAction) {
    const confirmed = await onPendingAction({ tool: 'bash_exec', input, analysis });
    if (!confirmed) return 'Action refusée par l\'utilisateur.';
  }
  const { stdout, stderr, code } = await exec(userId, serverId, input.command, 60000);
  return JSON.stringify({ stdout, stderr, exit_code: code });
}
 
async function executeReadFile({ userId, serverId, input }) {
  const pathCheck = validatePath(input.path);
  if (!pathCheck.valid) throw new Error(`Chemin invalide : ${pathCheck.reason}`);
  const safePath = shellEscape(input.path);
  const { stdout, stderr, code } = await exec(userId, serverId, `cat ${safePath}`, 15000);
  if (code !== 0) return `Erreur (code ${code}): ${stderr}`;
  return stdout;
}
 
async function executeWriteFile({ userId, serverId, input, onPendingAction }) {
  if (onPendingAction) {
    const confirmed = await onPendingAction({ tool: 'write_file', input, analysis: { level: 'write' } });
    if (!confirmed) return 'Écriture annulée par l\'utilisateur.';
  }
  const pathCheck = validatePath(input.path);
  if (!pathCheck.valid) throw new Error(`Chemin invalide : ${pathCheck.reason}`);
  const base64Content = Buffer.from(input.content, 'utf8').toString('base64');
  const safePath = shellEscape(input.path);
  const cmd = `echo ${shellEscape(base64Content)} | base64 -d > ${safePath}`;
  const { stderr, code } = await exec(userId, serverId, cmd, 15000);
  if (code !== 0) return `Erreur : ${stderr}`;
  return `Fichier ${input.path} écrit avec succès.`;
}
 
/**
 * Exécute un outil par son nom.
 * @returns {Promise<string>}
 */
async function executeTool(name, params) {
  if (name === 'bash_exec') return executeBashExec(params);
  if (name === 'read_file') return executeReadFile(params);
  if (name === 'write_file') return executeWriteFile(params);
  return 'Outil inconnu.';
}
 
module.exports = { SYSTEM_PROMPT, ANTHROPIC_TOOLS, OPENAI_TOOLS, executeTool };