agentic-api 2.0.314 → 2.0.585

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +37 -34
  2. package/dist/src/agents/prompts.d.ts +1 -1
  3. package/dist/src/agents/prompts.js +9 -7
  4. package/dist/src/agents/reducer.core.js +2 -2
  5. package/dist/src/agents/simulator.d.ts +33 -4
  6. package/dist/src/agents/simulator.dashboard.d.ts +140 -0
  7. package/dist/src/agents/simulator.dashboard.js +344 -0
  8. package/dist/src/agents/simulator.executor.d.ts +9 -3
  9. package/dist/src/agents/simulator.executor.js +43 -17
  10. package/dist/src/agents/simulator.js +103 -19
  11. package/dist/src/agents/simulator.prompts.d.ts +9 -8
  12. package/dist/src/agents/simulator.prompts.js +68 -62
  13. package/dist/src/agents/simulator.types.d.ts +39 -4
  14. package/dist/src/agents/simulator.utils.d.ts +22 -1
  15. package/dist/src/agents/simulator.utils.js +27 -2
  16. package/dist/src/execute/helpers.d.ts +75 -0
  17. package/dist/src/execute/helpers.js +139 -0
  18. package/dist/src/execute/index.d.ts +11 -0
  19. package/dist/src/execute/index.js +44 -0
  20. package/dist/src/execute/legacy.d.ts +46 -0
  21. package/dist/src/{execute.js → execute/legacy.js} +130 -232
  22. package/dist/src/execute/modelconfig.d.ts +29 -0
  23. package/dist/src/execute/modelconfig.js +72 -0
  24. package/dist/src/execute/responses.d.ts +55 -0
  25. package/dist/src/execute/responses.js +595 -0
  26. package/dist/src/execute/shared.d.ts +83 -0
  27. package/dist/src/execute/shared.js +188 -0
  28. package/dist/src/index.d.ts +5 -1
  29. package/dist/src/index.js +21 -2
  30. package/dist/src/llm/config.d.ts +25 -0
  31. package/dist/src/llm/config.js +38 -0
  32. package/dist/src/llm/index.d.ts +48 -0
  33. package/dist/src/llm/index.js +115 -0
  34. package/dist/src/llm/openai.d.ts +6 -0
  35. package/dist/src/llm/openai.js +154 -0
  36. package/dist/src/llm/pricing.d.ts +26 -0
  37. package/dist/src/llm/pricing.js +129 -0
  38. package/dist/src/llm/xai.d.ts +17 -0
  39. package/dist/src/llm/xai.js +90 -0
  40. package/dist/src/pricing.llm.d.ts +3 -15
  41. package/dist/src/pricing.llm.js +10 -230
  42. package/dist/src/prompts.d.ts +0 -1
  43. package/dist/src/prompts.js +51 -118
  44. package/dist/src/rag/embeddings.d.ts +5 -1
  45. package/dist/src/rag/embeddings.js +23 -7
  46. package/dist/src/rag/parser.js +1 -1
  47. package/dist/src/rag/rag.manager.d.ts +33 -2
  48. package/dist/src/rag/rag.manager.js +159 -61
  49. package/dist/src/rag/types.d.ts +2 -0
  50. package/dist/src/rag/usecase.js +8 -11
  51. package/dist/src/rules/git/git.e2e.helper.js +21 -2
  52. package/dist/src/rules/git/git.health.d.ts +4 -2
  53. package/dist/src/rules/git/git.health.js +113 -16
  54. package/dist/src/rules/git/index.d.ts +1 -1
  55. package/dist/src/rules/git/index.js +3 -2
  56. package/dist/src/rules/git/repo.d.ts +57 -7
  57. package/dist/src/rules/git/repo.js +326 -39
  58. package/dist/src/rules/git/repo.pr.d.ts +8 -0
  59. package/dist/src/rules/git/repo.pr.js +161 -13
  60. package/dist/src/rules/git/repo.tools.d.ts +5 -1
  61. package/dist/src/rules/git/repo.tools.js +54 -7
  62. package/dist/src/rules/types.d.ts +25 -0
  63. package/dist/src/rules/utils.matter.d.ts +0 -20
  64. package/dist/src/rules/utils.matter.js +58 -81
  65. package/dist/src/scrapper.js +3 -2
  66. package/dist/src/stategraph/stategraph.d.ts +26 -1
  67. package/dist/src/stategraph/stategraph.js +43 -2
  68. package/dist/src/stategraph/stategraph.storage.js +4 -0
  69. package/dist/src/stategraph/types.d.ts +5 -0
  70. package/dist/src/types.d.ts +42 -7
  71. package/dist/src/types.js +8 -7
  72. package/dist/src/usecase.js +1 -1
  73. package/dist/src/utils.d.ts +0 -8
  74. package/dist/src/utils.js +26 -29
  75. package/package.json +9 -7
  76. package/dist/src/execute.d.ts +0 -63
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SimulatorDashboard = void 0;
7
+ const fs_1 = require("fs");
8
+ const promises_1 = require("fs/promises");
9
+ const readline_1 = require("readline");
10
+ const path_1 = __importDefault(require("path"));
11
+ const simulator_1 = require("./simulator");
12
+ // ============================================================================
13
+ // CLASS: SimulatorDashboard
14
+ // ============================================================================
15
+ class SimulatorDashboard {
16
+ constructor(config) {
17
+ this.config = config;
18
+ this.simulator = null;
19
+ this.abortController = null;
20
+ this._currentOutputPath = null;
21
+ this._currentInputPath = null;
22
+ this.status = this.createInitialStatus();
23
+ }
24
+ /**
25
+ * Chemin du fichier output actuel (ou du dernier run)
26
+ */
27
+ get currentOutputPath() {
28
+ return this._currentOutputPath;
29
+ }
30
+ /**
31
+ * Chemin du fichier input actuel (ou du dernier run)
32
+ */
33
+ get currentInputPath() {
34
+ return this._currentInputPath;
35
+ }
36
+ createInitialStatus() {
37
+ return {
38
+ isRunning: false,
39
+ sessionId: null,
40
+ currentTest: 0,
41
+ totalTests: 0,
42
+ passed: 0,
43
+ failed: 0,
44
+ errors: 0,
45
+ startTime: null,
46
+ lastUpdate: null
47
+ };
48
+ }
49
+ /**
50
+ * Obtenir le status actuel (pour le contrôleur backend)
51
+ */
52
+ getStatus() {
53
+ return { ...this.status };
54
+ }
55
+ /**
56
+ * Vérifier si une exécution est en cours
57
+ */
58
+ isRunning() {
59
+ return this.status.isRunning;
60
+ }
61
+ /**
62
+ * Annuler l'exécution en cours
63
+ */
64
+ abort() {
65
+ if (this.abortController) {
66
+ this.abortController.abort();
67
+ }
68
+ }
69
+ /**
70
+ * Charger un fichier JSON d'entrée
71
+ */
72
+ async loadInputFile(filePath) {
73
+ const content = await (0, promises_1.readFile)(filePath, 'utf-8');
74
+ return JSON.parse(content);
75
+ }
76
+ /**
77
+ * Générer le chemin du fichier output basé sur le fichier input
78
+ * Exemple: tests/my-tests.json → tests/results.my-tests.jsonl
79
+ */
80
+ createOutputPath(inputPath) {
81
+ const dir = path_1.default.dirname(inputPath);
82
+ const basename = path_1.default.basename(inputPath, path_1.default.extname(inputPath));
83
+ return path_1.default.join(dir, `results.${basename}.jsonl`);
84
+ }
85
+ /**
86
+ * Charger les résultats JSONL existants (en cours ou terminés)
87
+ * Supporte le streaming partiel (fichier en cours d'écriture)
88
+ *
89
+ * @param inputPath - Chemin du fichier JSON d'entrée (génère automatiquement le output path)
90
+ * @returns Les lignes parsées ou null si le fichier n'existe pas
91
+ */
92
+ async loadResults(inputPath) {
93
+ // Déterminer le chemin du fichier output
94
+ let outputPath = null;
95
+ if (inputPath) {
96
+ outputPath = this.createOutputPath(inputPath);
97
+ }
98
+ else if (this._currentInputPath) {
99
+ outputPath = this.createOutputPath(this._currentInputPath);
100
+ }
101
+ else if (this._currentOutputPath) {
102
+ outputPath = this._currentOutputPath;
103
+ }
104
+ if (!outputPath || !(0, fs_1.existsSync)(outputPath)) {
105
+ return null;
106
+ }
107
+ return new Promise((resolve, reject) => {
108
+ const lines = [];
109
+ const stream = (0, fs_1.createReadStream)(outputPath, { encoding: 'utf-8' });
110
+ const rl = (0, readline_1.createInterface)({ input: stream, crlfDelay: Infinity });
111
+ rl.on('line', (line) => {
112
+ if (line.trim()) {
113
+ try {
114
+ lines.push(JSON.parse(line));
115
+ }
116
+ catch (e) {
117
+ // Ignorer les lignes mal formées (fichier en cours d'écriture)
118
+ }
119
+ }
120
+ });
121
+ rl.on('close', () => resolve(lines));
122
+ rl.on('error', reject);
123
+ });
124
+ }
125
+ /**
126
+ * Obtenir le résumé des résultats (dernière ligne type='end')
127
+ * @param inputPath - Chemin du fichier JSON d'entrée
128
+ */
129
+ async getResultsSummary(inputPath) {
130
+ const results = await this.loadResults(inputPath);
131
+ if (!results)
132
+ return null;
133
+ const endLine = results.find(r => r.type === 'end');
134
+ return endLine?.summary || null;
135
+ }
136
+ /**
137
+ * Vérifier si les résultats sont complets (contient une ligne 'end')
138
+ * @param inputPath - Chemin du fichier JSON d'entrée
139
+ */
140
+ async isResultsComplete(inputPath) {
141
+ const results = await this.loadResults(inputPath);
142
+ if (!results)
143
+ return false;
144
+ return results.some(r => r.type === 'end');
145
+ }
146
+ /**
147
+ * Exécuter les tests et écrire les résultats en JSONL
148
+ *
149
+ * @param input - Données d'entrée (ou chemin vers fichier JSON)
150
+ * @param outputPath - Chemin du fichier JSONL de sortie
151
+ * @param onLine - Callback optionnel pour chaque ligne JSONL (streaming)
152
+ */
153
+ async run(input, outputPath, onLine) {
154
+ // Stocker et charger l'input si c'est un chemin
155
+ const isInputFile = typeof input === 'string';
156
+ this._currentInputPath = isInputFile ? input : null;
157
+ const data = isInputFile
158
+ ? await this.loadInputFile(input)
159
+ : input;
160
+ // Générer automatiquement le outputPath si input est un fichier
161
+ const resolvedOutputPath = outputPath ?? (isInputFile ? this.createOutputPath(input) : undefined);
162
+ this._currentOutputPath = resolvedOutputPath || null;
163
+ // Initialiser le status
164
+ const sessionId = `session-${Date.now()}`;
165
+ this.status = {
166
+ isRunning: true,
167
+ sessionId,
168
+ currentTest: 0,
169
+ totalTests: data.tests.length,
170
+ passed: 0,
171
+ failed: 0,
172
+ errors: 0,
173
+ startTime: new Date(),
174
+ lastUpdate: new Date()
175
+ };
176
+ // Créer le simulateur avec config mergée
177
+ const mergedConfig = {
178
+ ...this.config,
179
+ ...data.config
180
+ };
181
+ this.simulator = new simulator_1.AgentSimulator(mergedConfig);
182
+ // Créer l'AbortController
183
+ this.abortController = new AbortController();
184
+ // Ouvrir le fichier de sortie si spécifié
185
+ let writeStream = null;
186
+ if (resolvedOutputPath) {
187
+ writeStream = (0, fs_1.createWriteStream)(resolvedOutputPath, { encoding: 'utf-8' });
188
+ }
189
+ const results = [];
190
+ const startTime = Date.now();
191
+ // Helper pour écrire une ligne
192
+ const writeLine = (line) => {
193
+ results.push(line);
194
+ const jsonLine = JSON.stringify(line) + '\n';
195
+ if (writeStream) {
196
+ writeStream.write(jsonLine);
197
+ }
198
+ if (onLine) {
199
+ onLine(line);
200
+ }
201
+ };
202
+ try {
203
+ // Écrire la ligne de début
204
+ writeLine({
205
+ type: 'start',
206
+ timestamp: new Date().toISOString(),
207
+ sessionId,
208
+ totalTests: data.tests.length
209
+ });
210
+ // Exécuter chaque test
211
+ for (let i = 0; i < data.tests.length; i++) {
212
+ // Vérifier si annulé
213
+ if (this.abortController.signal.aborted) {
214
+ break;
215
+ }
216
+ const testCase = data.tests[i];
217
+ const testId = testCase.id || `test-${i}`;
218
+ const testStartTime = Date.now();
219
+ // Mettre à jour le status
220
+ this.status.currentTest = i + 1;
221
+ this.status.lastUpdate = new Date();
222
+ try {
223
+ // Exécuter le test
224
+ const result = await this.simulator.testCase(testCase.scenario, testCase.case);
225
+ // Déterminer le status
226
+ const status = result.success ? 'completed' : 'failed';
227
+ // Mettre à jour les compteurs
228
+ if (result.success) {
229
+ this.status.passed++;
230
+ }
231
+ else {
232
+ this.status.failed++;
233
+ }
234
+ // Écrire le résultat
235
+ writeLine({
236
+ type: 'result',
237
+ timestamp: new Date().toISOString(),
238
+ testId,
239
+ testIndex: i,
240
+ name: testCase.name || testId,
241
+ description: testCase.scenario.description || testCase.scenario.goals,
242
+ query: testCase.case.query,
243
+ status,
244
+ success: result.success,
245
+ message: result.message,
246
+ error: result.error || undefined,
247
+ exchangeCount: result.exchangeCount,
248
+ messages: result.messages,
249
+ duration: Date.now() - testStartTime
250
+ });
251
+ }
252
+ catch (error) {
253
+ // Erreur d'exécution
254
+ this.status.errors++;
255
+ writeLine({
256
+ type: 'error',
257
+ timestamp: new Date().toISOString(),
258
+ testId,
259
+ testIndex: i,
260
+ name: testCase.name || testId,
261
+ description: testCase.scenario.description || testCase.scenario.goals,
262
+ query: testCase.case.query,
263
+ status: 'error',
264
+ success: false,
265
+ error: error.message || String(error),
266
+ duration: Date.now() - testStartTime
267
+ });
268
+ }
269
+ }
270
+ // Écrire la ligne de fin
271
+ writeLine({
272
+ type: 'end',
273
+ timestamp: new Date().toISOString(),
274
+ sessionId,
275
+ summary: {
276
+ total: data.tests.length,
277
+ passed: this.status.passed,
278
+ failed: this.status.failed,
279
+ errors: this.status.errors,
280
+ totalDuration: Date.now() - startTime
281
+ }
282
+ });
283
+ }
284
+ finally {
285
+ // Fermer le stream et attendre la fin de l'écriture
286
+ if (writeStream) {
287
+ await new Promise((resolve, reject) => {
288
+ writeStream.end(() => resolve());
289
+ writeStream.on('error', reject);
290
+ });
291
+ }
292
+ // Reset le status
293
+ this.status.isRunning = false;
294
+ this.status.lastUpdate = new Date();
295
+ this.simulator = null;
296
+ this.abortController = null;
297
+ }
298
+ return results;
299
+ }
300
+ /**
301
+ * Exécuter avec callback de streaming (pour SSE/WebSocket)
302
+ */
303
+ async runWithStream(input, onLine) {
304
+ return this.run(input, undefined, onLine);
305
+ }
306
+ /**
307
+ * Créer un fichier JSON d'exemple pour les tests
308
+ */
309
+ static createExampleInput() {
310
+ return {
311
+ name: "Example Test Suite",
312
+ description: "Exemple de suite de tests pour le simulator",
313
+ tests: [
314
+ {
315
+ id: "test-greeting",
316
+ name: "Test de salutation",
317
+ scenario: {
318
+ goals: "L'agent doit répondre poliment à la salutation",
319
+ persona: "Utilisateur poli et patient"
320
+ },
321
+ case: {
322
+ query: "Bonjour, comment allez-vous?"
323
+ // maxExchanges: 1 par défaut
324
+ // expectedTools: {} par défaut
325
+ }
326
+ },
327
+ {
328
+ id: "test-transfer",
329
+ name: "Test de transfert",
330
+ scenario: {
331
+ goals: "L'agent doit transférer vers l'agent spécialisé",
332
+ persona: "Utilisateur avec une demande spécifique"
333
+ },
334
+ case: {
335
+ query: "J'ai besoin d'aide spécialisée",
336
+ maxExchanges: 3,
337
+ expectedTools: { 'transferAgents': { gte: 1 } }
338
+ }
339
+ }
340
+ ]
341
+ };
342
+ }
343
+ }
344
+ exports.SimulatorDashboard = SimulatorDashboard;
@@ -1,7 +1,7 @@
1
- import { SimulatorConfig, ExecutionContext } from './simulator.types';
1
+ import { SimulatorConfig, ExecutionContext, SimulationScenario } from './simulator.types';
2
2
  export declare class AgentExecutor {
3
3
  private config;
4
- private simulatorAgent;
4
+ private simulatorAgentBase;
5
5
  private mockCacheInitializer?;
6
6
  private ragManager?;
7
7
  constructor(config: SimulatorConfig & {
@@ -9,8 +9,9 @@ export declare class AgentExecutor {
9
9
  });
10
10
  /**
11
11
  * Initialiser les contextes agent et simulateur
12
+ * @param scenario Le scenario de simulation complet avec persona, goals, result
12
13
  */
13
- initializeContexts(): Promise<ExecutionContext>;
14
+ initializeContexts(scenario: SimulationScenario): Promise<ExecutionContext>;
14
15
  /**
15
16
  * ✅ Exécuter l'agent testé et retourner sa réponse (extraction de agent-vs-agent.ts)
16
17
  */
@@ -19,6 +20,11 @@ export declare class AgentExecutor {
19
20
  * ✅ Exécuter le simulateur et retourner sa réponse (extraction de client-simulator.ts)
20
21
  */
21
22
  executeSimulator(context: ExecutionContext, query: string): Promise<string>;
23
+ /**
24
+ * ✅ Construire l'injection de contexte technique pour le simulateur
25
+ * Permet au simulateur de connaître les tools utilisés par l'agent testé
26
+ */
27
+ private buildAgentContextInjection;
22
28
  /**
23
29
  * ✅ Récupérer le dernier message de l'agent testé (extraction de agent-vs-agent.ts ligne 168-191)
24
30
  */
@@ -8,18 +8,13 @@ const simulator_prompts_1 = require("./simulator.prompts");
8
8
  class AgentExecutor {
9
9
  constructor(config) {
10
10
  this.config = config;
11
- // Construire les instructions complètes du simulateur
12
- const fullInstructions = simulator_prompts_1.GENERIC_SIMULATOR_PROMPT +
13
- (config.instructionEx ? `\n\n${config.instructionEx}` : '') +
14
- '\n\n**CRITICAL**: Your response after [DONE] must be valid JSON format only.';
15
- // console.log('---- DBG simulator fullInstructions',fullInstructions);
16
- // Agent simulateur simple pour éviter les dépendances complexes du ClientSimulator
17
- this.simulatorAgent = {
11
+ // Configuration de base du simulateur (instructions seront construites dynamiquement avec le scenario)
12
+ this.simulatorAgentBase = {
18
13
  name: "simple-simulator",
19
- model: ("MEDIUM-fast"), // JSON sera forcé via les instructions du prompt
14
+ model: ("HIGH-fast"),
20
15
  publicDescription: "Simulateur simple pour tests - TOUJOURS retourner JSON valide",
21
16
  cancelMemory: true,
22
- instructions: fullInstructions,
17
+ instructions: '', // Sera construit dans initializeContexts avec GENERIC_SIMULATOR_PROMPT
23
18
  tools: [],
24
19
  downstreamAgents: []
25
20
  };
@@ -42,8 +37,9 @@ class AgentExecutor {
42
37
  }
43
38
  /**
44
39
  * Initialiser les contextes agent et simulateur
40
+ * @param scenario Le scenario de simulation complet avec persona, goals, result
45
41
  */
46
- async initializeContexts() {
42
+ async initializeContexts(scenario) {
47
43
  const sessionId = `simulation-${Date.now()}`;
48
44
  const agentContext = {
49
45
  user: { id: `test-user`, role: 'user', uid: `test-${sessionId}` },
@@ -63,9 +59,17 @@ class AgentExecutor {
63
59
  if (this.mockCacheInitializer) {
64
60
  await this.mockCacheInitializer(agentContext.session.id);
65
61
  }
62
+ // ✅ Construire les instructions complètes avec le scénario intégré via la fonction
63
+ const fullInstructions = (0, simulator_prompts_1.GENERIC_SIMULATOR_PROMPT)(scenario, this.config.instructionEx);
64
+ if (this.config.verbose)
65
+ console.log('---- DBG fullInstructions', fullInstructions);
66
66
  return {
67
67
  agentContext,
68
68
  simulatorContext,
69
+ simulatorAgent: {
70
+ ...this.simulatorAgentBase,
71
+ instructions: fullInstructions
72
+ },
69
73
  conversationHistory: [],
70
74
  exchangeCount: 0,
71
75
  lastExecution: {
@@ -73,8 +77,8 @@ class AgentExecutor {
73
77
  startQuery: '',
74
78
  actions: [],
75
79
  lastMessage: '',
76
- usage: { prompt: 0, completion: 0, total: 0, cost: 0 },
77
- moreThinkin: false,
80
+ usage: { prompt: 0, completion: 0, total: 0, cost: 0 }
81
+ // moreThinkin removed (obsolete)
78
82
  }
79
83
  };
80
84
  }
@@ -111,12 +115,15 @@ class AgentExecutor {
111
115
  * ✅ Exécuter le simulateur et retourner sa réponse (extraction de client-simulator.ts)
112
116
  */
113
117
  async executeSimulator(context, query) {
114
- // Utiliser le simulateur dynamique avec instructions étendues
115
- // console.log('DEBUG: Exécution du simulateur avec query:\n', query);
116
- await (0, execute_1.executeAgentSet)([this.simulatorAgent], context.simulatorContext, {
117
- query,
118
+ // Utiliser le simulateur avec les instructions complètes (incluant le scénario)
119
+ // Injecter le contexte technique si disponible
120
+ const agentContextInjection = this.buildAgentContextInjection(context);
121
+ const enrichedQuery = agentContextInjection ? `${query}\n\n${agentContextInjection}` : query;
122
+ // console.log('DEBUG: Exécution du simulateur avec query:\n', enrichedQuery);
123
+ await (0, execute_1.executeAgentSet)([context.simulatorAgent], context.simulatorContext, {
124
+ query: enrichedQuery,
118
125
  home: 'simple-simulator',
119
- stdout: { write: () => { }, end: () => { }, writableEnded: false },
126
+ stdout: execute_1.DummyWritable,
120
127
  verbose: false,
121
128
  debug: false
122
129
  });
@@ -124,6 +131,25 @@ class AgentExecutor {
124
131
  context.conversationHistory.push(`Simulator: ${response}`);
125
132
  return response;
126
133
  }
134
+ /**
135
+ * ✅ Construire l'injection de contexte technique pour le simulateur
136
+ * Permet au simulateur de connaître les tools utilisés par l'agent testé
137
+ */
138
+ buildAgentContextInjection(context) {
139
+ if (!context.lastExecution || !context.lastExecution.actions || context.lastExecution.actions.length === 0) {
140
+ return '';
141
+ }
142
+ // Extraire les noms des tools appelés (sans les arguments)
143
+ const tools = context.lastExecution.actions.map(action => action.action);
144
+ if (tools.length === 0) {
145
+ return '';
146
+ }
147
+ const contextData = {
148
+ tools: tools,
149
+ exchangeCount: context.exchangeCount
150
+ };
151
+ return `<agent-context>\n${JSON.stringify(contextData, null, 2)}\n</agent-context>`;
152
+ }
127
153
  /**
128
154
  * ✅ Récupérer le dernier message de l'agent testé (extraction de agent-vs-agent.ts ligne 168-191)
129
155
  */
@@ -2,35 +2,94 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AgentSimulator = void 0;
4
4
  const simulator_executor_1 = require("./simulator.executor");
5
- const simulator_prompts_1 = require("./simulator.prompts");
6
5
  class AgentSimulator {
7
6
  constructor(config) {
8
7
  this.config = config;
9
8
  this.executor = new simulator_executor_1.AgentExecutor(config);
10
9
  }
10
+ // ============================================================================
11
+ // NOUVELLE API : testCase(scenario, case)
12
+ // ============================================================================
11
13
  /**
14
+ * Exécuter un cas de test avec scénario et paramètres séparés
15
+ *
16
+ * @param scenario - Contexte stable (goals, persona, result)
17
+ * @param testCase - Paramètres du test (query, maxExchanges, model, expectedTools)
18
+ * @returns SimulationResult
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const scenario = {
23
+ * goals: 'Obtenir le nombre secret 1942',
24
+ * persona: PERSONA_PATIENT,
25
+ * result: '{"success": boolean, "error": string}'
26
+ * };
27
+ *
28
+ * const result = await simulator.testCase(scenario, {
29
+ * query: 'À quel nombre penses-tu?',
30
+ * maxExchanges: 3, // défaut: 1 (oneshot)
31
+ * expectedTools: { 'transferAgents': { equal: 1 } } // défaut: {}
32
+ * });
33
+ * ```
34
+ */
35
+ async testCase(scenario, testCase) {
36
+ // Appliquer les défauts
37
+ const resolvedCase = {
38
+ query: testCase.query,
39
+ maxExchanges: testCase.maxExchanges ?? 1, // Défaut: 1 (oneshot)
40
+ expectedTools: testCase.expectedTools ?? {}, // Défaut: pas de validation
41
+ model: testCase.model,
42
+ onMessage: testCase.onMessage
43
+ };
44
+ // TODO: Support model override (Phase 2)
45
+ // if (resolvedCase.model) {
46
+ // this.overrideModel(resolvedCase.model);
47
+ // }
48
+ // Convertir TestScenario vers SimulationScenario et déléguer
49
+ return this.executeSimulation({
50
+ scenario: {
51
+ goals: scenario.goals,
52
+ persona: scenario.persona,
53
+ result: scenario.result || '{"success": boolean, "explain": string, "error": string}'
54
+ },
55
+ query: resolvedCase.query,
56
+ maxExchanges: resolvedCase.maxExchanges,
57
+ expectedTool: resolvedCase.expectedTools,
58
+ onMessage: resolvedCase.onMessage
59
+ });
60
+ }
61
+ // ============================================================================
62
+ // ANCIENNE API : executeSimulation(options) - deprecated
63
+ // ============================================================================
64
+ /**
65
+ * @deprecated Utiliser testCase(scenario, case) à la place
66
+ *
12
67
  * Exécuter la simulation complète
13
68
  *
69
+ * Architecture :
70
+ * - Le scénario (Personnalité, Question, Objectifs, Format JSON) est injecté UNE SEULE FOIS
71
+ * dans les instructions du simulateur au moment de l'initialisation (AVANT la boucle).
72
+ *
14
73
  * Format de la query passée à l'agent testé :
15
- * - Message initial : réponse du simulateur après analyse du scenario
74
+ * - Message initial : query fournie par l'utilisateur
16
75
  * - Messages suivants : réponse conversationnelle du simulateur (sans tags d'évaluation)
17
76
  *
18
77
  * Format de la query passée au simulateur :
19
- * - Message initial : buildSimulatorQuery(scenario) - format structuré avec SIMULATION SCENARIO
20
- * - Messages suivants : réponse de l'agent testé (pour évaluation et réaction)
78
+ * - Instructions système : scénario complet intégré via GENERIC_SIMULATOR_PROMPT
79
+ * - Tous les messages : réponse directe de l'agent testé (agentResponse)
21
80
  */
22
81
  async executeSimulation(options) {
23
- const context = await this.executor.initializeContexts();
82
+ // Initialiser les contextes avec le scenario complet
83
+ const context = await this.executor.initializeContexts(options.scenario);
24
84
  const allMessages = [];
25
85
  let lastAgentMessage = '';
26
86
  let exchangeCounter = 0; // Compteur d'échanges (user+assistant)
27
87
  try {
28
- // Construire la query formatée avec buildSimulatorQuery()
29
- // Les instructions supplémentaires sont déjà intégrées dans le constructeur de l'executor
30
- const scenarioQuery = (0, simulator_prompts_1.buildSimulatorQuery)(options.scenario, options.query);
31
- // Générer le message initial du simulateur avec le scénario
32
- let currentUserQuery = await this.executor.executeSimulator(context, scenarioQuery);
33
- // Stocker le message initial du simulateur
88
+ // Pour le premier message, utiliser directement la query fournie
89
+ // Le simulateur ne doit PAS reformuler la question initiale
90
+ const initialQuery = options.query || options.scenario.testQuery || '';
91
+ let currentUserQuery = initialQuery;
92
+ // Stocker le message initial (query brute de l'utilisateur)
34
93
  const initialMsg = { content: currentUserQuery, role: 'user' };
35
94
  allMessages.push(initialMsg);
36
95
  if (options.onMessage) {
@@ -39,6 +98,7 @@ class AgentSimulator {
39
98
  // Boucle de conversation - maxExchanges = nombre de paires (user+assistant)
40
99
  while (exchangeCounter < options.maxExchanges) {
41
100
  // Agent testé répond et retourne sa réponse
101
+ // La première fois la query est options.query, les fois suivantes c'est la réponse conversationnelle du simulateur
42
102
  const agentResponse = await this.executor.executeAgent(context, currentUserQuery);
43
103
  lastAgentMessage = agentResponse;
44
104
  // Stocker la réponse de l'agent
@@ -49,9 +109,9 @@ class AgentSimulator {
49
109
  }
50
110
  // Incrémenter le compteur après la réponse de l'agent (assistant)
51
111
  exchangeCounter++;
52
- // Simulateur évalue et répond
112
+ // Passer directement agentResponse au simulateur (scénario déjà dans les instructions)
53
113
  const simulatorResult = await this.executor.executeSimulator(context, agentResponse);
54
- // console.log(context.agentContext.messages);
114
+ // console.log('---- DBG simulatorResult',simulatorResult);
55
115
  // Vérifier si terminé
56
116
  if (this.isSimulationComplete(simulatorResult)) {
57
117
  const expectedFormat = options.scenario.result || options.scenario.testResult || '{"success": boolean, "error": string}';
@@ -150,8 +210,8 @@ class AgentSimulator {
150
210
  startQuery: result.execution?.startQuery,
151
211
  actions: result.execution?.actions?.map((elem) => elem.action) || [],
152
212
  lastMessage: result.execution?.lastMessage,
153
- usage: result.execution?.usage,
154
- moreThinkin: result.execution?.moreThinkin
213
+ usage: result.execution?.usage
214
+ // moreThinkin removed (obsolete)
155
215
  };
156
216
  const execution = {
157
217
  ...result.execution,
@@ -224,9 +284,24 @@ class AgentSimulator {
224
284
  };
225
285
  }
226
286
  extractConversationalPart(response) {
227
- // Extraire la partie conversationnelle avant les tags d'évaluation
228
- const tagIndex = response.search(/\[(DONE|SIMULATION_COMPLETE|TERMINE|BUG_)/);
229
- return tagIndex !== -1 ? response.substring(0, tagIndex).trim() : response;
287
+ // Extraire la partie conversationnelle avant les tags d'évaluation ou d'observation
288
+ // Filtrer tous les tags système : [DONE], [OBSERVATEUR SILENCIEUX], [À NOTER], etc.
289
+ const tagIndex = response.search(/\[(DONE|SIMULATION_COMPLETE|TERMINE|BUG_|OBSERVATEUR|À NOTER|NOTE|ANALYSE)/i);
290
+ if (tagIndex !== -1) {
291
+ return response.substring(0, tagIndex).trim();
292
+ }
293
+ // Filtrer aussi les lignes qui commencent par "Agent :" ou "[...]" (méta-commentaires)
294
+ const lines = response.split('\n');
295
+ const conversationalLines = lines.filter(line => {
296
+ const trimmed = line.trim();
297
+ // Exclure les lignes qui sont des méta-commentaires
298
+ if (trimmed.startsWith('Agent :') ||
299
+ trimmed.startsWith('[') && trimmed.includes(']') && !trimmed.match(/^\[[^\]]{1,3}\]/)) {
300
+ return false;
301
+ }
302
+ return true;
303
+ });
304
+ return conversationalLines.join('\n').trim();
230
305
  }
231
306
  /**
232
307
  * Générer le rapport final en cas de timeout
@@ -250,8 +325,17 @@ class AgentSimulator {
250
325
  const errors = [];
251
326
  for (const [toolName, constraint] of Object.entries(expected)) {
252
327
  const { count } = this.executionActionCount(toolName);
328
+ // Validation equal (égalité exacte)
253
329
  if (constraint.equal !== undefined && count !== constraint.equal) {
254
- errors.push(`Tool '${toolName}': expected ${constraint.equal}, got ${count}`);
330
+ errors.push(`Tool '${toolName}': expected equal to ${constraint.equal}, got ${count}`);
331
+ }
332
+ // Validation gte (greater than or equal - supérieur ou égal)
333
+ if (constraint.gte !== undefined && count < constraint.gte) {
334
+ errors.push(`Tool '${toolName}': expected >= ${constraint.gte}, got ${count}`);
335
+ }
336
+ // Validation lte (less than or equal - inférieur ou égal)
337
+ if (constraint.lte !== undefined && count > constraint.lte) {
338
+ errors.push(`Tool '${toolName}': expected <= ${constraint.lte}, got ${count}`);
255
339
  }
256
340
  }
257
341
  return {