All files / src/services claude.js

0% Statements 0/36
0% Branches 0/13
0% Functions 0/6
0% Lines 0/33

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                                                                                                                                                                                                           
/**
 * Service Claude — Intégration Anthropic avec tool use.
 *
 * Utilise le module tools partagé pour l'exécution des outils.
 *
 * @module services/claude
 */
 
const Anthropic = require('@anthropic-ai/sdk');
const { SYSTEM_PROMPT, ANTHROPIC_TOOLS, executeTool } = require('./tools');
 
const MAX_TURNS = 10;
 
/**
 * Chat avec Claude en mode agentic (tool use).
 *
 * @param {Object} params
 * @param {string} params.userId
 * @param {string} params.serverId
 * @param {string} params.apiKey
 * @param {string} params.model
 * @param {Array} params.messages — Historique de conversation (format Anthropic)
 * @param {string} [params.context]
 * @param {Function} [params.onPendingAction]
 * @returns {Promise<{ reply: string, messages: Array }>}
 */
/**
 * Extrait le texte final d'une reponse Claude.
 * @param {Array} content — Blocs de contenu
 * @returns {string}
 */
function extractTextReply(content) {
  return content
    .filter((b) => b.type === 'text')
    .map((b) => b.text)
    .join('\n');
}
 
/**
 * Execute tous les tool_use d'une reponse Claude et retourne les resultats.
 * @param {Array} content — Blocs de contenu de la reponse
 * @param {Object} ctx — Contexte (userId, serverId, onPendingAction)
 * @returns {Promise<Array>}
 */
async function processToolCalls(content, ctx) {
  const toolResults = [];
  for (const block of content) {
    if (block.type !== 'tool_use') continue;
    const result = await executeOneToolCall(block, ctx);
    toolResults.push(result);
  }
  return toolResults;
}
 
/**
 * Execute un seul appel d'outil.
 * @param {Object} block — Bloc tool_use
 * @param {Object} ctx — Contexte
 * @returns {Promise<Object>}
 */
async function executeOneToolCall(block, ctx) {
  const { name, id, input } = block;
  try {
    const content = await executeTool(name, { userId: ctx.userId, serverId: ctx.serverId, input, onPendingAction: ctx.onPendingAction });
    return { type: 'tool_result', tool_use_id: id, content };
  } catch (err) {
    return { type: 'tool_result', tool_use_id: id, content: `Erreur : ${err.message}`, is_error: true };
  }
}
 
async function chat({ userId, serverId, apiKey, model, messages, context, allowTools = true, onPendingAction }) {
  const client = new Anthropic({ apiKey });
 
  const systemWithContext = context
    ? `${SYSTEM_PROMPT}\n\nContexte actuel : ${context}`
    : SYSTEM_PROMPT;
 
  let currentMessages = [...messages];
  const tools = allowTools ? ANTHROPIC_TOOLS : [];
 
  for (let turn = 0; turn < MAX_TURNS; turn++) {
    const createParams = { model, max_tokens: 4096, system: systemWithContext, messages: currentMessages };
    if (tools.length > 0) createParams.tools = tools;
 
    const response = await client.messages.create(createParams);
    currentMessages.push({ role: 'assistant', content: response.content });
 
    if (response.stop_reason === 'end_turn') {
      return { reply: extractTextReply(response.content), messages: currentMessages };
    }
 
    if (response.stop_reason === 'tool_use') {
      const toolResults = await processToolCalls(response.content, { userId, serverId, onPendingAction });
      currentMessages.push({ role: 'user', content: toolResults });
    }
  }
 
  return { reply: 'Limite de tours atteinte. Reformulez votre demande.', messages: currentMessages };
}
 
module.exports = { chat };