agent-master-devsecops 0.0.0-stage → 5.1.0

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 (61) hide show
  1. package/README.md +204 -2
  2. package/agents/AGENT_DEVSECOPS.md +32 -0
  3. package/agents/AGENT_INFRA.md +31 -0
  4. package/agents/AGENT_ORCHESTRATOR.md +39 -0
  5. package/agents/AGENT_QA.md +58 -0
  6. package/agents/AGENT_SCRUM.md +23 -0
  7. package/agents/AGENT_SRE.md +22 -0
  8. package/agents/AGENT_UIUX.md +26 -0
  9. package/docs/ARCHITECTURE.md +152 -0
  10. package/docs/CALCULATIONS.md +17 -0
  11. package/docs/CHANGELOG.md +112 -0
  12. package/docs/ENGINEERING_RULES.md +56 -0
  13. package/docs/IDEAS.md +17 -0
  14. package/docs/LOCAL_BACKLOG.md +20 -0
  15. package/docs/STATE.md +111 -0
  16. package/docs/playbooks/PLAYBOOK_CIRCUIT_BREAKER_LLM.md +58 -0
  17. package/docs/playbooks/PLAYBOOK_OPENTELEMETRY_TRACING.md +58 -0
  18. package/package.json +57 -4
  19. package/scripts/auditors/a11y_auditor.js +462 -0
  20. package/scripts/auditors/code_quality_auditor.js +444 -0
  21. package/scripts/auditors/contract_test_validator.js +396 -0
  22. package/scripts/auditors/eval_harness.js +213 -0
  23. package/scripts/auditors/git_hygiene_linter.js +391 -0
  24. package/scripts/auditors/pr_reviewer.js +627 -0
  25. package/scripts/auditors/sast_scanner.js +442 -0
  26. package/scripts/auditors/sca_dependency_auditor.js +673 -0
  27. package/scripts/bin/bootstrap_cli.js +597 -0
  28. package/scripts/bin/domain_idea_miner.js +658 -0
  29. package/scripts/bin/smart_onboarding.js +905 -0
  30. package/scripts/bin/web_inspector.js +578 -0
  31. package/scripts/core/architecture_advisor.js +378 -0
  32. package/scripts/core/ast_skeletonizer.js +413 -0
  33. package/scripts/core/autonomous_remediation_engine.js +305 -0
  34. package/scripts/core/circuit_breaker.js +546 -0
  35. package/scripts/core/context_compressor.js +237 -0
  36. package/scripts/core/fast_feature_scaffold.js +840 -0
  37. package/scripts/core/hot_state_cache.js +238 -0
  38. package/scripts/core/knowledge_gateway.js +888 -0
  39. package/scripts/core/magic_prompt_generator.js +250 -0
  40. package/scripts/core/multi_agent_orchestrator.js +421 -0
  41. package/scripts/core/otel_tracer.js +335 -0
  42. package/scripts/core/perf_benchmark.js +212 -0
  43. package/scripts/core/sarif_engine.js +299 -0
  44. package/scripts/core/sre_remediation_engine.js +236 -0
  45. package/scripts/core/universal_packager.js +357 -0
  46. package/scripts/integrations/atlassian_client.js +600 -0
  47. package/scripts/integrations/atlassian_sync_action.js +576 -0
  48. package/scripts/integrations/confluence_sync.js +684 -0
  49. package/scripts/integrations/credentials_guard.js +500 -0
  50. package/scripts/integrations/jira_manager.js +722 -0
  51. package/scripts/mcp/handlers/atlassian_handlers.js +300 -0
  52. package/scripts/mcp/handlers/core_handlers.js +419 -0
  53. package/scripts/mcp/handlers/qa_handlers.js +502 -0
  54. package/scripts/mcp/handlers/rules_handlers.js +379 -0
  55. package/scripts/mcp/handlers/scaffold_handlers.js +334 -0
  56. package/scripts/mcp/handlers/sre_handlers.js +443 -0
  57. package/scripts/mcp/resources_registry.js +453 -0
  58. package/scripts/mcp/server.js +408 -0
  59. package/scripts/mcp/tool_handlers.js +444 -0
  60. package/scripts/mcp/tools_registry.js +1123 -0
  61. package/scripts/mcp_server.js +236 -0
@@ -0,0 +1,546 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scripts/circuit_breaker.js
4
+ *
5
+ * Circuit Breaker Inteligente Multi-Provedor para LLMs (AGDEV-9 / I-10).
6
+ * Abstração de resiliência e auto-cura (CLOSED, OPEN, HALF_OPEN) com fallback automático
7
+ * entre provedores de IA (Anthropic, OpenAI, Groq, Ollama) diante de erros HTTP 429/5xx ou timeouts.
8
+ */
9
+
10
+ const { createSarifReport } = require('./sarif_engine.js');
11
+
12
+ const STATES = {
13
+ CLOSED: 'CLOSED', // Operação normal
14
+ OPEN: 'OPEN', // Circuito rompido; chamadas desviadas para fallback
15
+ HALF_OPEN: 'HALF_OPEN' // Sonda de teste para verificar recuperação
16
+ };
17
+
18
+ class CircuitBreaker {
19
+ constructor(name, options = {}) {
20
+ this.name = name;
21
+ this.failureThreshold = options.failureThreshold || 3;
22
+ this.recoveryTimeoutMs = options.recoveryTimeoutMs || 30000;
23
+ this.state = STATES.CLOSED;
24
+ this.failureCount = 0;
25
+ this.successCount = 0;
26
+ this.lastFailureTime = null;
27
+ this.lastStateChange = Date.now();
28
+ }
29
+
30
+ isOpen() {
31
+ if (this.state === STATES.OPEN) {
32
+ const now = Date.now();
33
+ if (now - this.lastFailureTime >= this.recoveryTimeoutMs) {
34
+ this.state = STATES.HALF_OPEN;
35
+ this.lastStateChange = now;
36
+ return false;
37
+ }
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+
43
+ recordSuccess() {
44
+ this.failureCount = 0;
45
+ this.successCount++;
46
+ if (this.state === STATES.HALF_OPEN) {
47
+ this.state = STATES.CLOSED;
48
+ this.lastStateChange = Date.now();
49
+ }
50
+ }
51
+
52
+ recordFailure(error) {
53
+ this.failureCount++;
54
+ this.lastFailureTime = Date.now();
55
+
56
+ if (this.state === STATES.HALF_OPEN || this.failureCount >= this.failureThreshold) {
57
+ this.state = STATES.OPEN;
58
+ this.lastStateChange = Date.now();
59
+ }
60
+ }
61
+
62
+ async execute(primaryFn, fallbackFn = null) {
63
+ if (this.isOpen()) {
64
+ if (fallbackFn) {
65
+ return {
66
+ result: await fallbackFn(new Error(`Circuito ${this.name} está ABERTO (OPEN). Redirecionando para fallback.`)),
67
+ circuit: this.name,
68
+ state: this.state,
69
+ fallbackExecuted: true
70
+ };
71
+ }
72
+ throw new Error(`Circuito ${this.name} está ABERTO (OPEN). Nenhuma chamada permitida.`);
73
+ }
74
+
75
+ try {
76
+ const result = await primaryFn();
77
+ this.recordSuccess();
78
+ return {
79
+ result,
80
+ circuit: this.name,
81
+ state: this.state,
82
+ fallbackExecuted: false
83
+ };
84
+ } catch (err) {
85
+ this.recordFailure(err);
86
+ if (fallbackFn) {
87
+ return {
88
+ result: await fallbackFn(err),
89
+ circuit: this.name,
90
+ state: this.state,
91
+ fallbackExecuted: true,
92
+ error: err.message
93
+ };
94
+ }
95
+ throw err;
96
+ }
97
+ }
98
+
99
+ executeSync(primaryFn, fallbackFn = null) {
100
+ if (this.isOpen()) {
101
+ if (fallbackFn) {
102
+ return {
103
+ result: fallbackFn(new Error(`Circuito ${this.name} está ABERTO (OPEN). Redirecionando para fallback.`)),
104
+ circuit: this.name,
105
+ state: this.state,
106
+ fallbackExecuted: true
107
+ };
108
+ }
109
+ throw new Error(`Circuito ${this.name} está ABERTO (OPEN). Nenhuma chamada permitida.`);
110
+ }
111
+
112
+ try {
113
+ const result = primaryFn();
114
+ this.recordSuccess();
115
+ return {
116
+ result,
117
+ circuit: this.name,
118
+ state: this.state,
119
+ fallbackExecuted: false
120
+ };
121
+ } catch (err) {
122
+ this.recordFailure(err);
123
+ if (fallbackFn) {
124
+ return {
125
+ result: fallbackFn(err),
126
+ circuit: this.name,
127
+ state: this.state,
128
+ fallbackExecuted: true,
129
+ error: err.message
130
+ };
131
+ }
132
+ throw err;
133
+ }
134
+ }
135
+
136
+ reset() {
137
+ this.state = STATES.CLOSED;
138
+ this.failureCount = 0;
139
+ this.successCount = 0;
140
+ this.lastFailureTime = null;
141
+ this.lastStateChange = Date.now();
142
+ }
143
+
144
+ getStatus() {
145
+ return {
146
+ name: this.name,
147
+ state: this.state,
148
+ failureCount: this.failureCount,
149
+ failureThreshold: this.failureThreshold,
150
+ recoveryTimeoutMs: this.recoveryTimeoutMs,
151
+ isOpen: this.isOpen()
152
+ };
153
+ }
154
+
155
+ auditToSarif() {
156
+ const status = this.getStatus();
157
+ const findings = [];
158
+
159
+ if (status.isOpen) {
160
+ findings.push({
161
+ ruleId: 'RULE-CIRCUIT-OPEN',
162
+ name: 'CircuitBreakerTripped',
163
+ severity: 'HIGH',
164
+ message: `O circuito ${status.name} está ABERTO após ${status.failureCount} falhas consecutivas.`,
165
+ remediation: 'Verifique a saúde do provedor ou acione o failover automático.'
166
+ });
167
+ }
168
+
169
+ return createSarifReport({
170
+ toolName: `agent-master-circuit-breaker-${status.name}`,
171
+ rules: [
172
+ {
173
+ id: 'RULE-CIRCUIT-OPEN',
174
+ name: 'CircuitBreakerTripped',
175
+ description: 'Identifica quando um provedor de IA entrou em estado de circuito rompido',
176
+ severity: 'HIGH',
177
+ cwe: 'CWE-400'
178
+ }
179
+ ],
180
+ findings,
181
+ properties: {
182
+ circuitName: status.name,
183
+ state: status.state,
184
+ failureCount: status.failureCount
185
+ }
186
+ });
187
+ }
188
+
189
+ formatReport() {
190
+ const s = this.getStatus();
191
+ return [
192
+ `### Status do Circuito [${s.name}]`,
193
+ `- **Estado:** \`${s.state}\``,
194
+ `- **Falhas:** ${s.failureCount} / ${s.failureThreshold}`,
195
+ `- **Tempo de Recuperação:** ${s.recoveryTimeoutMs}ms`,
196
+ `- **Aberto (Bloqueado):** ${s.isOpen ? 'Sim (Failover ativo)' : 'Não (Operação normal)'}`
197
+ ].join('\n');
198
+ }
199
+ }
200
+
201
+ class MultiProviderRouter {
202
+ constructor(providers = ['anthropic', 'openai', 'groq', 'ollama'], options = {}) {
203
+ this.circuits = new Map();
204
+ providers.forEach(p => {
205
+ this.circuits.set(p, new CircuitBreaker(p, options));
206
+ });
207
+ this.providerChain = providers;
208
+ }
209
+
210
+ async dispatch(taskFnMap) {
211
+ const attempted = [];
212
+
213
+ for (const provider of this.providerChain) {
214
+ const circuit = this.circuits.get(provider);
215
+ attempted.push(provider);
216
+
217
+ if (!circuit.isOpen()) {
218
+ const fn = taskFnMap[provider] || taskFnMap['default'];
219
+ if (!fn) continue;
220
+
221
+ try {
222
+ const res = await circuit.execute(fn);
223
+ return {
224
+ success: true,
225
+ providerUsed: provider,
226
+ attempted,
227
+ result: res.result,
228
+ circuitStates: this.getAllStatuses()
229
+ };
230
+ } catch (err) {
231
+ // Erro registrado pelo circuit breaker; continua para o próximo provedor na cadeia
232
+ continue;
233
+ }
234
+ }
235
+ }
236
+
237
+ return {
238
+ success: false,
239
+ error: 'Todos os provedores de IA estão indisponíveis ou com circuito ABERTO.',
240
+ attempted,
241
+ circuitStates: this.getAllStatuses()
242
+ };
243
+ }
244
+
245
+ dispatchSync(taskFnMap) {
246
+ const attempted = [];
247
+
248
+ for (const provider of this.providerChain) {
249
+ const circuit = this.circuits.get(provider);
250
+ attempted.push(provider);
251
+
252
+ if (!circuit.isOpen()) {
253
+ const fn = taskFnMap[provider] || taskFnMap['default'];
254
+ if (!fn) continue;
255
+
256
+ try {
257
+ const res = circuit.executeSync(fn);
258
+ return {
259
+ success: true,
260
+ providerUsed: provider,
261
+ attempted,
262
+ result: res.result,
263
+ circuitStates: this.getAllStatuses()
264
+ };
265
+ } catch (err) {
266
+ continue;
267
+ }
268
+ }
269
+ }
270
+
271
+ return {
272
+ success: false,
273
+ error: 'Todos os provedores de IA estão indisponíveis ou com circuito ABERTO.',
274
+ attempted,
275
+ circuitStates: this.getAllStatuses()
276
+ };
277
+ }
278
+
279
+ async executePromptWithFailover(prompt, options = {}) {
280
+ const taskMap = {};
281
+ for (const provider of this.providerChain) {
282
+ taskMap[provider] = () => callLlmHttp(provider, prompt, options);
283
+ }
284
+ return this.dispatch(taskMap);
285
+ }
286
+
287
+ getAllStatuses() {
288
+ const statuses = {};
289
+ for (const [name, circuit] of this.circuits.entries()) {
290
+ statuses[name] = circuit.getStatus();
291
+ }
292
+ return statuses;
293
+ }
294
+
295
+ resetAll() {
296
+ for (const circuit of this.circuits.values()) {
297
+ circuit.reset();
298
+ }
299
+ }
300
+
301
+ auditToSarif() {
302
+ const statuses = this.getAllStatuses();
303
+ const findings = [];
304
+ const openCircuits = [];
305
+
306
+ for (const [name, st] of Object.entries(statuses)) {
307
+ if (st.isOpen) {
308
+ openCircuits.push(name);
309
+ findings.push({
310
+ ruleId: 'RULE-CIRCUIT-OPEN',
311
+ name: 'CircuitBreakerTripped',
312
+ severity: 'HIGH',
313
+ message: `O provedor ${name} está com o circuito ABERTO (${st.failureCount} falhas).`,
314
+ remediation: `Ative o failover automático para outro provedor da cadeia (${this.providerChain.join(', ')}).`
315
+ });
316
+ }
317
+ }
318
+
319
+ return createSarifReport({
320
+ toolName: 'agent-master-llm-router',
321
+ rules: [
322
+ {
323
+ id: 'RULE-CIRCUIT-OPEN',
324
+ name: 'CircuitBreakerTripped',
325
+ description: 'Identifica provedores LLM indisponíveis ou rompidos na cadeia de resiliência',
326
+ severity: 'HIGH',
327
+ cwe: 'CWE-400'
328
+ }
329
+ ],
330
+ findings,
331
+ properties: {
332
+ totalProviders: this.providerChain.length,
333
+ openCircuitsCount: openCircuits.length,
334
+ openCircuits
335
+ }
336
+ });
337
+ }
338
+
339
+ formatReport() {
340
+ const statuses = this.getAllStatuses();
341
+ const rows = Object.entries(statuses).map(([name, s]) =>
342
+ `| \`${name}\` | \`${s.state}\` | ${s.failureCount}/${s.failureThreshold} | ${s.isOpen ? '⚠️ Sim' : '✅ Não'} |`
343
+ ).join('\n');
344
+
345
+ return [
346
+ '### Relatório de Resiliência — Roteador Multi-Provedor LLM',
347
+ '| Provedor | Estado | Falhas | Circuito Aberto |',
348
+ '|---|---|---|---|',
349
+ rows
350
+ ].join('\n');
351
+ }
352
+ }
353
+
354
+ function callLlmHttp(provider, prompt, options = {}) {
355
+ if (options.transportAdapter) {
356
+ return options.transportAdapter(provider, prompt, options);
357
+ }
358
+
359
+ const timeoutMs = options.timeoutMs || 10000;
360
+
361
+ return new Promise((resolve, reject) => {
362
+ let urlStr = '';
363
+ const headers = { 'Content-Type': 'application/json' };
364
+ let payload = {};
365
+
366
+ if (provider === 'openai') {
367
+ const apiKey = options.openaiApiKey || process.env.OPENAI_API_KEY;
368
+ if (!apiKey) return reject(new Error('OPENAI_API_KEY não configurada no ambiente.'));
369
+ urlStr = 'https://api.openai.com/v1/chat/completions';
370
+ headers['Authorization'] = `Bearer ${apiKey}`;
371
+ payload = {
372
+ model: options.openaiModel || 'gpt-4o-mini',
373
+ messages: [{ role: 'user', content: prompt }]
374
+ };
375
+ } else if (provider === 'anthropic') {
376
+ const apiKey = options.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
377
+ if (!apiKey) return reject(new Error('ANTHROPIC_API_KEY não configurada no ambiente.'));
378
+ urlStr = 'https://api.anthropic.com/v1/messages';
379
+ headers['x-api-key'] = apiKey;
380
+ headers['anthropic-version'] = '2023-06-01';
381
+ payload = {
382
+ model: options.anthropicModel || 'claude-3-5-haiku-20241022',
383
+ max_tokens: 1024,
384
+ messages: [{ role: 'user', content: prompt }]
385
+ };
386
+ } else if (provider === 'groq') {
387
+ const apiKey = options.groqApiKey || process.env.GROQ_API_KEY;
388
+ if (!apiKey) return reject(new Error('GROQ_API_KEY não configurada no ambiente.'));
389
+ urlStr = 'https://api.groq.com/openai/v1/chat/completions';
390
+ headers['Authorization'] = `Bearer ${apiKey}`;
391
+ payload = {
392
+ model: options.groqModel || 'llama-3.3-70b-versatile',
393
+ messages: [{ role: 'user', content: prompt }]
394
+ };
395
+ } else if (provider === 'ollama') {
396
+ const host = options.ollamaHost || process.env.OLLAMA_HOST || 'http://localhost:11434';
397
+ urlStr = `${host.replace(/\/+$/, '')}/api/generate`;
398
+ payload = {
399
+ model: options.ollamaModel || 'llama3',
400
+ prompt,
401
+ stream: false
402
+ };
403
+ } else {
404
+ return reject(new Error(`Provedor desconhecido: ${provider}`));
405
+ }
406
+
407
+ const parsed = new URL(urlStr);
408
+ const transport = parsed.protocol === 'https:' ? require('node:https') : require('node:http');
409
+ const bodyStr = JSON.stringify(payload);
410
+ headers['Content-Length'] = Buffer.byteLength(bodyStr);
411
+
412
+ const req = transport.request(parsed, {
413
+ method: 'POST',
414
+ headers,
415
+ timeout: timeoutMs
416
+ }, (res) => {
417
+ let data = '';
418
+ res.setEncoding('utf8');
419
+ res.on('data', chunk => { data += chunk; });
420
+ res.on('end', () => {
421
+ if (res.statusCode >= 200 && res.statusCode < 300) {
422
+ try {
423
+ const parsedRes = JSON.parse(data);
424
+ let textOutput = '';
425
+ if (provider === 'anthropic') {
426
+ textOutput = parsedRes.content && parsedRes.content[0] ? parsedRes.content[0].text : '';
427
+ } else if (provider === 'ollama') {
428
+ textOutput = parsedRes.response || '';
429
+ } else {
430
+ textOutput = parsedRes.choices && parsedRes.choices[0] && parsedRes.choices[0].message ? parsedRes.choices[0].message.content : '';
431
+ }
432
+ resolve({
433
+ provider,
434
+ statusCode: res.statusCode,
435
+ text: textOutput,
436
+ raw: parsedRes
437
+ });
438
+ } catch (jsonErr) {
439
+ resolve({ provider, statusCode: res.statusCode, text: data });
440
+ }
441
+ } else {
442
+ reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage || 'Erro de API'} - ${data.slice(0, 200)}`));
443
+ }
444
+ });
445
+ });
446
+
447
+ req.on('timeout', () => {
448
+ req.destroy();
449
+ reject(new Error(`Timeout de conexão após ${timeoutMs}ms ao chamar ${provider}.`));
450
+ });
451
+
452
+ req.on('error', (err) => {
453
+ reject(new Error(`Erro de rede no provedor ${provider}: ${err.message}`));
454
+ });
455
+
456
+ req.write(bodyStr);
457
+ req.end();
458
+ });
459
+ }
460
+
461
+
462
+ async function runCli() {
463
+ console.log('=== Simulação de Resiliência Multi-Provedor com Circuit Breaker (I-10) ===');
464
+ const router = new MultiProviderRouter(['anthropic', 'openai', 'groq', 'ollama'], { failureThreshold: 2 });
465
+
466
+ // Simula indisponibilidade em anthropic e openai
467
+ const taskMap = {
468
+ anthropic: async () => { throw new Error('HTTP 429: Too Many Requests (Rate limit exceeded)'); },
469
+ openai: async () => { throw new Error('HTTP 503: Service Unavailable'); },
470
+ groq: async () => { return 'Resposta gerada com sucesso via Groq (Llama 3.3 70B)'; },
471
+ ollama: async () => { return 'Resposta local via Ollama'; }
472
+ };
473
+
474
+ const dispatch1 = await router.dispatch(taskMap);
475
+ console.log('\nDespacho 1 (Anthropic & OpenAI falham -> Fallback para Groq):');
476
+ console.log(`Provedor Utilizado: ${dispatch1.providerUsed}`);
477
+ console.log(`Tentativas: ${dispatch1.attempted.join(' -> ')}`);
478
+ console.log(`Resultado: ${dispatch1.result}`);
479
+
480
+ console.log('\nStatus dos Circuitos:');
481
+ console.log(JSON.stringify(dispatch1.circuitStates, null, 2));
482
+ }
483
+
484
+ if (require.main === module) {
485
+ runCli().catch(console.error);
486
+ }
487
+
488
+
489
+ class AntiThrashingGuard {
490
+ constructor(options = {}) {
491
+ this.maxIdenticalFailures = options.maxIdenticalFailures || 2;
492
+ this.history = [];
493
+ this.thrashingTripped = false;
494
+ }
495
+
496
+ normalizeError(error) {
497
+ if (!error) return 'unknown_error';
498
+ const msg = typeof error === 'string' ? error : (error.message || String(error));
499
+ return msg
500
+ .replace(/:\d+:\d+/g, '')
501
+ .replace(/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\b/g, '')
502
+ .replace(/0x[0-9a-fA-F]+/g, '')
503
+ .trim();
504
+ }
505
+
506
+ recordAttempt(error = null) {
507
+ if (!error) {
508
+ this.history = [];
509
+ this.thrashingTripped = false;
510
+ return { isThrashing: false, count: 0 };
511
+ }
512
+
513
+ const norm = this.normalizeError(error);
514
+ this.history.push({ signature: norm, timestamp: Date.now() });
515
+
516
+ if (this.history.length >= this.maxIdenticalFailures) {
517
+ const recent = this.history.slice(-this.maxIdenticalFailures);
518
+ const allIdentical = recent.every(h => h.signature === norm);
519
+ if (allIdentical) {
520
+ this.thrashingTripped = true;
521
+ return {
522
+ isThrashing: true,
523
+ count: this.history.length,
524
+ signature: norm,
525
+ message: `[ANTI-THRASHING FINOPS VETO]: Detectadas ${this.maxIdenticalFailures} falhas consecutivas com erro idêntico ("${norm.substring(0, 80)}..."). Execução autônoma interrompida para evitar queima descontrolada de tokens.`
526
+ };
527
+ }
528
+ }
529
+
530
+ return { isThrashing: false, count: this.history.length, signature: norm };
531
+ }
532
+
533
+ reset() {
534
+ this.history = [];
535
+ this.thrashingTripped = false;
536
+ }
537
+ }
538
+
539
+
540
+ module.exports = {
541
+ STATES,
542
+ CircuitBreaker,
543
+ MultiProviderRouter,
544
+ AntiThrashingGuard,
545
+ callLlmHttp
546
+ };