@areumtecnologia/autonomouscustomerserviceagent 2.0.1

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.
package/src/index.js ADDED
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * AutonomousCustomerServiceAgent
5
+ * ──────────────────────────────
6
+ * Agente de atendimento autônomo com:
7
+ * 1. Sessões internas com TTL e renovação por atividade
8
+ * 2. Rastreamento externo de tentativas de exploração (não depende do LLM)
9
+ * 3. Retry com backoff exponencial + jitter
10
+ * 4. Timeout por turno e por tool via AbortController
11
+ * 5. Agentic loop completo: tool call → resultado → resposta contextualizada
12
+ * 6. Registro programático de Tools customizadas (schema + handler)
13
+ * 7. Consciência temporal e humanização de boas-vindas no primeiro contato
14
+ */
15
+
16
+ const { AgentEvents } = require('./AgentEvents');
17
+ const { AgentManager } = require('./AgentManager');
18
+ const { AgentConfig } = require('./AgentConfig');
19
+ const { AutonomousCustomerServiceAgent } = require('./AutonomousCustomerServiceAgent');
20
+ const { Type, ThinkingLevel } = require('@google/genai');
21
+
22
+ module.exports = { AutonomousCustomerServiceAgent, AgentEvents, Type, ThinkingLevel, AgentManager, AgentConfig };
package/src/utils.js ADDED
@@ -0,0 +1,55 @@
1
+
2
+ // ─────────────────────────────────────────────────────────────────────────────
3
+ // withRetry — backoff exponencial com jitter
4
+ // ─────────────────────────────────────────────────────────────────────────────
5
+
6
+ /**
7
+ * @template T
8
+ * @param {() => Promise<T>} fn
9
+ * @param {{
10
+ * maxAttempts?: number,
11
+ * baseDelayMs?: number,
12
+ * maxDelayMs?: number,
13
+ * retryIf?: (err: Error) => boolean,
14
+ * onRetry?: (info: { attempt: number, delay: number, error: Error }) => void
15
+ * }} opts
16
+ * @returns {Promise<T>}
17
+ */
18
+ async function withRetry(fn, {
19
+ maxAttempts = 3,
20
+ baseDelayMs = 900,
21
+ maxDelayMs = 9_000,
22
+ retryIf = () => true,
23
+ onRetry,
24
+ } = {}) {
25
+ let attempt = 0;
26
+ while (true) {
27
+ try {
28
+ return await fn();
29
+ } catch (err) {
30
+ attempt++;
31
+
32
+ const shouldRetry =
33
+ attempt < maxAttempts &&
34
+ retryIf(err);
35
+
36
+ if (!shouldRetry) {
37
+ throw err;
38
+ }
39
+
40
+ const exponential = baseDelayMs * (2 ** (attempt - 1));
41
+ const jitter = Math.random() * baseDelayMs * 0.5;
42
+ const delay = Math.min(exponential + jitter, maxDelayMs);
43
+
44
+ onRetry?.({
45
+ attempt,
46
+ delay,
47
+ error: err,
48
+ });
49
+
50
+ await new Promise(r => setTimeout(r, delay));
51
+ }
52
+ }
53
+ }
54
+
55
+ module.exports = { withRetry };
@@ -0,0 +1,104 @@
1
+ /*
2
+ Exemplo de API RESTful usando Express para gerenciar sessões
3
+ e processar mensagens com o AutonomousCustomerServiceAgent.
4
+
5
+ Para executar este exemplo:
6
+ npm install express
7
+ node tests/test-api-restful.js
8
+ */
9
+
10
+ require('dotenv').config();
11
+ const express = require('express');
12
+ const { AutonomousCustomerServiceAgent, Type } = require('../src/index');
13
+
14
+ const app = express();
15
+ app.use(express.json());
16
+
17
+ const agent = new AutonomousCustomerServiceAgent({
18
+ apiKey: process.env.GOOGLE_GEMINI_API_KEY,
19
+ company: {
20
+ name: 'Poranduba Amazônia Turismo',
21
+ details: 'Ecoturismo premium na Amazônia. Especialistas em turismo sustentável desde 2010.',
22
+ },
23
+ agent: {
24
+ name: 'Monnalisa',
25
+ mission: {
26
+ objective: 'Atuar como agente de vendas especialista em qualificação e conversão de leads.',
27
+ instructions: `
28
+ 1. Cumprimente o lead de forma profissional e acolhedora.
29
+ 2. Descubra as necessidades do lead antes de usar ferramentas.
30
+ 3. Use ferramentas quando necessário para obter dados atualizados.
31
+ 4. Responda com precisão e contextualização.
32
+ `,
33
+ },
34
+ },
35
+ failureHandlingMode: 'async',
36
+ retryScheduleMinutes: 5,
37
+ retryScheduleAttempts: 24,
38
+ unavailabilityMessage: 'Estamos com uma indisponibilidade temporária. Entraremos em contato assim que o problema for sanado.',
39
+ });
40
+
41
+ // Registrar um exemplo de tool para demonstração
42
+ agent.registerTool({
43
+ name: 'get_current_datetime',
44
+ description: 'Retorna a data e hora atual no fuso horário do Brasil.',
45
+ parameters: { type: Type.OBJECT, properties: {} },
46
+ }, async () => new Date().toLocaleString('pt-BR', { timeZone: 'America/Sao_Paulo' }));
47
+
48
+ app.post('/sessions', (req, res) => {
49
+ const { name, phone, origin } = req.body;
50
+ if (!name || !phone) {
51
+ return res.status(400).json({ error: 'name and phone are required' });
52
+ }
53
+
54
+ const sessionId = agent.createSession({
55
+ name,
56
+ phone,
57
+ origin: origin || { type: 'api', description: 'Lead via API RESTful' },
58
+ });
59
+
60
+ res.status(201).json({ sessionId });
61
+ });
62
+
63
+ app.post('/sessions/:sessionId/message', async (req, res) => {
64
+ const { sessionId } = req.params;
65
+ const { message } = req.body;
66
+
67
+ if (!message) {
68
+ return res.status(400).json({ error: 'message is required' });
69
+ }
70
+
71
+ try {
72
+ const response = await agent.processMessage(message, sessionId);
73
+ res.json(response);
74
+ } catch (err) {
75
+ res.status(500).json({ error: err.message || String(err) });
76
+ }
77
+ });
78
+
79
+ app.post('/sessions/:sessionId/clear', (req, res) => {
80
+ const { sessionId } = req.params;
81
+ const cleared = agent.clearSession(sessionId);
82
+ if (!cleared) {
83
+ return res.status(404).json({ error: 'session not found' });
84
+ }
85
+ res.json({ cleared: true });
86
+ });
87
+
88
+ app.get('/sessions/:sessionId', (req, res) => {
89
+ const { sessionId } = req.params;
90
+ const session = agent.getSession(sessionId);
91
+ if (!session) {
92
+ return res.status(404).json({ error: 'session not found' });
93
+ }
94
+ res.json(session);
95
+ });
96
+
97
+ app.get('/', (req, res) => {
98
+ res.json({ message: 'AutonomousCustomerServiceAgent REST API is running.' });
99
+ });
100
+
101
+ const PORT = process.env.PORT || 3000;
102
+ app.listen(PORT, () => {
103
+ console.log(`REST API listening on http://localhost:${PORT}`);
104
+ });
package/tests/test.js ADDED
@@ -0,0 +1,279 @@
1
+ require('dotenv').config();
2
+
3
+ const GOOGLE_GEMINI_API_KEY = process.env.GOOGLE_GEMINI_API_KEY;
4
+ const { AutonomousCustomerServiceAgent, Type, AgentEvents, AgentConfig } = require('../src') //require('@areumtecnologia/autonomouscustomerserviceagent');
5
+
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+ // Exemplo de uso completo (multi-turno com tool call real)
8
+ //
9
+ // Recursos de Tratamento de Erros e Recovery:
10
+ // • SERVICE_UNAVAILABLE: Emitido quando há erro irrecuperável
11
+ // • RECOVERY_SCHEDULED: Tentativa automática agendada em X minutos
12
+ // • RECOVERY_ATTEMPT: Tentativa de recuperação em execução
13
+ // • inErrorState: Flag que marca sessão em erro (graceful degradation)
14
+ // • Resposta de indisponibilidade customizável (errorMessages)
15
+ // ─────────────────────────────────────────────────────────────────────────────
16
+
17
+ async function example() {
18
+ const productCategories = ['Passeios', 'Trilhas', 'Hospedagem', 'VIP', 'Noturno', 'Luxo', 'Econômico', 'Iniciantes', 'Barco'];
19
+
20
+ const customerAgent = new AutonomousCustomerServiceAgent({
21
+ apiKey: GOOGLE_GEMINI_API_KEY,
22
+ // model: 'gemma-4-31b-it', // 'gemma-4-26b-a4b-it',
23
+ // temperature: 0.1,
24
+ agent: new AgentConfig(
25
+ 'Monnalisa',
26
+ 'Poranduba Amazônia Turismo',
27
+ 'Ecoturismo premium na Amazônia. Especialistas em turismo sustentável desde 2010.',
28
+ 'Sua missão é atuar como agente de vendas, especializado em qualificação e conversão de leads.',
29
+ `1. Cumprimente o lead de forma imediata, profissional e acolhedora usando expressões comuns do dia-a-dia (Bom dia, Boa tarde, Boa noite, etc).
30
+ Nota: Obedecer a essa diretiva demonstra alta disponibilidade, cria conexão emocional e aumenta a probabilidade de conversão do lead.
31
+ 2. Descubra as necessidades e interesses do lead, antes de chamar ferramentas.
32
+ 3. Após o lead expressar claramente suas necessidades ou questões, utilize as ferramentas disponíveis para obter dados atualizados.
33
+ 4. Forneça respostas precisas e contextualizadas, incorporando os resultados das tools de forma natural.
34
+ 5. Classifique o lead com base nas informações coletadas.
35
+ 6. Efetive a venda, se aplicável, utilizando as ferramentas de checkout disponíveis.
36
+ 7. Mantenha o foco no direcionamento da conversa. Se o lead tentar desviar do assunto ou fazer perguntas irrelevantes, gentilmente redirecione a conversa de volta para o que você precisa saber.
37
+ 8. Ao final da conversa, agradeça o lead pelo contato e informe que você está à disposição para futuras dúvidas ou necessidades.`,
38
+ 'pt-BR'
39
+ )
40
+ });
41
+
42
+ // ── Eventos ───────────────────────────────────────────────────────────────
43
+ customerAgent
44
+ .on(AgentEvents.SESSION_CREATED, ({ session }) => console.log(`[Sessão] Criada: ${session.id}`))
45
+ .on(AgentEvents.SESSION_CLEARED, ({ session }) => console.log(`[Sessão] Limpa: ${session.id}`))
46
+ .on(AgentEvents.TURN_START, ({ depth, session }) => console.log(`[Loop] Turno ${depth} — sessão ${session.id}`))
47
+ .on(AgentEvents.TURN_END, ({ depth, session }) => console.log(`[Loop] Turno ${depth} finalizado — sessão ${session.id}`))
48
+ .on(AgentEvents.RESPONSE, ({ response, session, purchase_probability }) => {
49
+ console.log('\x1b[32m%s\x1b[0m', `[Agente] Sessão ${session.id}:`, response);
50
+ if (purchase_probability !== undefined) {
51
+ console.log(` → Probabilidade de compra estimada: ${(purchase_probability * 100).toFixed(1)}%`);
52
+ }
53
+ })
54
+ // .on(AgentEvents.RAW_RESPONSE, ({ rawResponse, session }) => console.log(`[Raw Response] Sessão ${session.id}:`, rawResponse, rawResponse.candidates[0].content.parts))
55
+ .on(AgentEvents.TOOL_CALL, ({ name, args }) => console.log(`[Tool →] ${name}`, args))
56
+ .on(AgentEvents.TOOL_RESULT, ({ name, result }) => console.log(`[Tool ←] ${name}:`, result))
57
+ .on(AgentEvents.RETRY, ({ attempt, delay, error }) => {
58
+ const msg = error?.message || error?.error?.message || String(error);
59
+ console.warn(`[Retry] Tentativa ${attempt} em ${Math.round(delay)}ms - ${msg}`);
60
+ })
61
+ .on(AgentEvents.VULNERABILITY_EXPLORATION_DETECTED, ({ session, attempts }) => {
62
+ console.error(`\x1b[31m%s\x1b[0m`, `[Vulnerability Exploration Detected] - ${session.id} has made ${attempts} attempts. Session details: ${JSON.stringify(session)}`);
63
+ })
64
+ .on(AgentEvents.ERROR, ({ error, source }) => {
65
+ const msg = error?.message || error?.error?.message || String(error);
66
+ console.error(`\x1b[31m%s\x1b[0m`, `[Erro]${source ? ` [${source}]` : ''} - ${msg}`);
67
+ });
68
+
69
+
70
+ // ── Registra NOVA tool programaticamente (informando o Schema completo) ───
71
+ customerAgent.registerTool({
72
+ name: 'get_current_datetime',
73
+ description: 'Retorna a data e hora atual no fuso horário do Brasil (America/Sao_Paulo).',
74
+ parameters: { type: Type.OBJECT, properties: {} },
75
+ }, async () =>
76
+ new Date().toLocaleString('pt-BR', { timeZone: 'America/Sao_Paulo' })
77
+ );
78
+
79
+ customerAgent.registerTool({
80
+ name: 'get_product_data',
81
+ description: 'Obtém informações de produtos e serviços da empresa (preços, disponibilidade, detalhes) com base em tags ou palavras-chave.',
82
+ parameters: {
83
+ type: Type.OBJECT,
84
+ properties: {
85
+ tags: {
86
+ type: Type.ARRAY,
87
+ items: { type: Type.STRING, enum: productCategories },
88
+ description: 'Categorias a consultar. Null ou omitido retorna todas.',
89
+ },
90
+ },
91
+ },
92
+ }, async ({ tags } = {}) => {
93
+ const categories = tags?.length ? tags : productCategories;
94
+ const products = [
95
+ { id: 1, name: 'Passeios de Barco', price: 'R$ 200', details: 'Passeio de 4 horas pelos igarapés.', tags: ['Passeios', 'Barco'] },
96
+ { id: 2, name: 'Trilhas', price: 'R$ 150', details: 'Trilha de 3 horas na floresta.', tags: ['Trilhas'] },
97
+ { id: 3, name: 'Hospedagem', price: 'R$ 500/noite', details: 'Quarto confortável com vista para o rio.', tags: ['Hospedagem'] },
98
+ { id: 4, name: 'Passeios de Barco VIP', price: 'R$ 400', details: 'Passeio exclusivo de 6 horas com guia privado.', tags: ['Passeios', 'Barco', 'VIP'] },
99
+ { id: 5, name: 'Trilhas Noturnas', price: 'R$ 180', details: 'Trilha de 2 horas para observar a vida noturna da floresta.', tags: ['Trilhas', 'Noturno'] },
100
+ { id: 6, name: 'Hospedagem Luxo', price: 'R$ 800/noite', details: 'Suíte de luxo com todas as comodidades.', tags: ['Hospedagem', 'Luxo'] },
101
+ { id: 7, name: 'Passeios de Barco Econômico', price: 'R$ 100', details: 'Passeio de 2 horas pelos igarapés.', tags: ['Passeios', 'Barco', 'Econômico'] },
102
+ { id: 8, name: 'Trilhas para Iniciantes', price: 'R$ 120', details: 'Trilha de 1 hora para iniciantes.', tags: ['Trilhas', 'Iniciantes'] },
103
+ { id: 9, name: 'Hospedagem Econômica', price: 'R$ 300/noite', details: 'Quarto econômico com vista para o jardim.', tags: ['Hospedagem', 'Econômica'] },
104
+ ];
105
+ return JSON.stringify(products.filter(p => categories.some(category => p.tags.includes(category))));
106
+ });
107
+
108
+ customerAgent.registerTool({
109
+ name: 'check_availability',
110
+ description: 'Verifica a disponibilidade de vagas para uma reserva em determinada data.',
111
+ parameters: {
112
+ type: Type.OBJECT,
113
+ properties: {
114
+ tags: {
115
+ type: Type.ARRAY,
116
+ items: { type: Type.STRING, enum: productCategories },
117
+ description: 'Categorias a consultar. Null ou omitido retorna todas.',
118
+ },
119
+ date: { type: Type.STRING, description: 'Data desejada no formato YYYY-MM-DD' }
120
+ },
121
+ required: ['tags', 'date']
122
+ }
123
+ }, async ({ tags, date }, _signal) => {
124
+ return JSON.stringify({ servico: tags, data: date, disponivel: true, vagas_restantes: 5 });
125
+ });
126
+
127
+ customerAgent.registerTool({
128
+ name: 'checkout',
129
+ description: 'Finaliza a compra usando informacoes do lead, dos produtos/servicos selecionados e retorna os detalhes da transação.',
130
+ parameters: {
131
+ type: Type.OBJECT,
132
+ properties: {
133
+ product_id: {
134
+ type: Type.INTEGER,
135
+ description: 'ID do produto a ser comprado.'
136
+ },
137
+ customer_info: {
138
+ type: Type.OBJECT,
139
+ properties: {
140
+ name: { type: Type.STRING, description: 'Nome do cliente.' },
141
+ phone: { type: Type.STRING, description: 'Telefone do cliente.' },
142
+ email: { type: Type.STRING, description: 'Email do cliente.' },
143
+ },
144
+ }
145
+ }
146
+ },
147
+ }, async () => {
148
+ /* Lógica de checkout */
149
+ return JSON.stringify({ success: true, order_id: 'ABC123', message: 'Compra finalizada com sucesso!' });
150
+ }
151
+ );
152
+
153
+
154
+ // ── Conversa multi-turno ──────────────────────────────────────────────────
155
+ const session = customerAgent.createSession(Date.now().toString(), {
156
+ name: 'Renan',
157
+ phone: '5591981648646',
158
+ origin: { id: '12345', type: 'whatsapp', description: 'Lead via WhatsApp.' }
159
+ });
160
+
161
+ customerAgent.registerTool({
162
+ name: 'clear_session',
163
+ description: 'Limpa a sessão atual, após concluir a conversa.',
164
+ parameters: { type: Type.OBJECT, properties: {} },
165
+ }, async () => {
166
+ console.log('\x1b[90m%s\x1b[0m', '[Tool] O Agente chamou clear_session - limpando sessão para encerrar conversa.');
167
+ customerAgent.clearSession(session.id)
168
+
169
+ }
170
+ );
171
+
172
+ // Tool para registrar a classificacao e probabilidade de compra do lead. Deve ser forcada para ser executada
173
+ // customerAgent.registerTool({
174
+ // name: 'reclassify_lead',
175
+ // description: `This tool records the lead's classification and estimated purchase probability, based on conversation analysis and lead data. The agent must call this tool at the end of each interaction to ensure that the lead's qualification information is always up-to-date. Run this tool at least once after identifying the user as a lead. The initial value of purchase_probability is 0.`,
176
+ // parameters: {
177
+ // type: Type.OBJECT,
178
+ // properties: {
179
+ // classification: {
180
+ // type: Type.STRING,
181
+ // enum: ['qualifying', 'unqualified', 'cold', 'warm', 'hot', 'converted'],
182
+ // description: 'Lead classification based on the conversation and lead data. "qualifying" is a default neutral classification, while "unqualified", "cold", "warm", and "hot" indicate increasing levels of sales readiness.',
183
+ // },
184
+ // purchase_probability: {
185
+ // type: Type.NUMBER,
186
+ // description: 'Estimated purchase probability as a number between 0 and 1. This should be based on the model’s analysis of the conversation and lead data, and can be used for prioritization and follow-up strategies.'
187
+ // },
188
+ // },
189
+ // }
190
+ // }, async ({ classification, purchase_probability }) => {
191
+ // console.log('\x1b[34m%s\x1b[0m', `[Tool] classify_lead called with classification="${classification}" and purchase_probability=${purchase_probability}`);
192
+ // // Aqui você pode implementar lógica adicional, como salvar a classificação em um banco de dados ou atualizar o CRM
193
+ // return { success: true };
194
+ // });
195
+
196
+ // Turno 0 → Teste de System prompt do agente
197
+ // const p0 = "Você tem alguma incoerência ou contradição nas suas instruções? Se sim, explique qual é e como você lida com isso.";
198
+ // console.log('\x1b[33m%s\x1b[0m', `\n[Lead]: ${p0}`); // Simula mensagem do lead
199
+ // const r0 = await customerAgent.processMessage(p0, session.id);
200
+
201
+ // Observação: Qualquer erro durante processMessage agora resulta em:
202
+ // 1. Evento ERROR emitido com detalhes do erro
203
+ // 2. Evento SERVICE_UNAVAILABLE emitido
204
+ // 3. Resposta de indisponibilidade retornada ao lead (graceful degradation)
205
+ // 4. Recovery automático agendado (RECOVERY_SCHEDULED)
206
+ // 5. Timer dispara após X segundos (RECOVERY_ATTEMPT com status='awaiting_lead_message')
207
+ // 6. Próxima mensagem do lead tenta processar normalmente
208
+ // 7. Se bem-sucedido: RECOVERY_ATTEMPT com status='recovered' e inErrorState é zerado
209
+ // 8. Se falhar novamente: volta para passo 1 (erro é retentado indefinidamente)
210
+ //
211
+ // IMPORTANTE: As mensagens continuam sendo enfileiradas e processadas normalmente!
212
+
213
+ // Turno 1 → agente atenderá o lead com boas-vindas
214
+ console.log('\x1b[33m%s\x1b[0m', `\n[Lead]: Olá!`); // Simula mensagem do lead
215
+ const r1 = await customerAgent.processMessage('Olá!', session.id);
216
+
217
+ // Turno 2 → agente usará get_product_data
218
+ console.log('\x1b[33m%s\x1b[0m', `\n[Lead]: Quais os valores dos passeios de barco?`); // Simula mensagem do lead
219
+ const r2 = await customerAgent.processMessage('Quais os valores dos passeios de barco?', session.id);
220
+
221
+ // Turno 3 → agente usará a tool recém criada programaticamente 'check_availability'
222
+ console.log('\x1b[33m%s\x1b[0m', `\n[Lead]: Tem disponibilidade para o dia 30 de maio?`); // Simula mensagem do lead
223
+ const r3 = await customerAgent.processMessage('Tem disponibilidade para o dia 30 de maio?', session.id);
224
+
225
+ // Turno 4 - Cliente aceita a oferta
226
+ console.log('\x1b[33m%s\x1b[0m', `\n[Lead]: Perfeito, quero reservar!`); // Simula mensagem do lead
227
+ const r4 = await customerAgent.processMessage('Perfeito, quero reservar!', session.id);
228
+
229
+ // Turno 5 - Cliente fornece informações para checkout
230
+ console.log('\x1b[33m%s\x1b[0m', `\n[Lead]: Meu nome é Renan, meu telefone é 5591981648646 e meu email é renan@example.com`); // Simula mensagem do lead
231
+ const r5 = await customerAgent.processMessage('Meu nome é Renan, meu telefone é 5591981648646 e meu email é renan@example.com', session.id);
232
+
233
+ // Mostra o numero de sessoes ativas concomitantes
234
+ console.log('\x1b[36m%s\x1b[0m', `\n[Sessões Ativas] ${customerAgent.activeSessionsCount()} sessão(ões) ativa(s) no momento.`);
235
+ }
236
+
237
+ // ─────────────────────────────────────────────────────────────────────────────
238
+ // Verificar estado de erro em resposta do agente:
239
+ //
240
+ // if (response.service_unavailable) {
241
+ // console.log('Serviço indisponível, recovery agendado');
242
+ // console.log('Tentativas:', response.recovery_attempts);
243
+ // }
244
+ // ─────────────────────────────────────────────────────────────────────────────
245
+
246
+ // Delay entre execuções
247
+ function delay(ms) {
248
+ return new Promise(resolve => setTimeout(resolve, ms));
249
+ }
250
+
251
+ // Executa o teste consecutivamente para validar estabilidade e performance
252
+ (async () => {
253
+ const startTime = Date.now();
254
+ let successCount = 0;
255
+ let failureCount = 0;
256
+
257
+ for (let i = 0; i < 5; i++) {
258
+ try {
259
+ console.log(`\n\x1b[36m%s\x1b[0m`, `\n=== Execução de Teste ${i + 1} ===`);
260
+ await example();
261
+ successCount++;
262
+ } catch (err) {
263
+ const msg = err?.message || String(err);
264
+ console.error('\x1b[31m%s\x1b[0m', `[Erro Fatal]`, err);
265
+ failureCount++;
266
+ }
267
+ // Aguarda 2 segundos entre testes para evitar rate limiting
268
+ if (i < 4) {
269
+ console.log('\x1b[90m%s\x1b[0m', '[Aguardando 2s antes do próximo teste...]');
270
+ await delay(2000);
271
+ }
272
+ }
273
+
274
+ const duration = (Date.now() - startTime) / 1000;
275
+ console.log('\n\x1b[36m%s\x1b[0m', '\n=== Testes Concluídos ===');
276
+ console.log(`✓ Sucessos: ${successCount}`);
277
+ console.log(`✗ Falhas: ${failureCount}`);
278
+ console.log(`⏱ Duração: ${duration.toFixed(1)}s`);
279
+ })();