bimmo-cli 1.2.3 → 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/bin/bimmo +6 -3
- package/package.json +5 -2
- package/src/config.js +48 -1
- package/src/interface.js +87 -49
- package/src/orchestrator.js +84 -0
- package/src/providers/anthropic.js +6 -6
- package/src/providers/base.js +2 -2
- package/src/providers/gemini.js +6 -7
- package/src/providers/grok.js +4 -3
- package/src/providers/ollama.js +4 -4
- package/src/providers/openai.js +9 -8
package/bin/bimmo
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Silencia o aviso de depreciação do punycode que polui o terminal em versões novas do Node
|
|
4
|
+
process.removeAllListeners('warning');
|
|
5
|
+
|
|
3
6
|
import { program } from 'commander';
|
|
4
7
|
import { startInteractive } from '../src/interface.js';
|
|
5
8
|
import { configure } from '../src/config.js';
|
|
@@ -7,13 +10,13 @@ import { configure } from '../src/config.js';
|
|
|
7
10
|
program
|
|
8
11
|
.name('bimmo')
|
|
9
12
|
.description('bimmo — Sua IA universal no terminal (verde & lavanda)')
|
|
10
|
-
.version('1.
|
|
13
|
+
.version('1.2.4');
|
|
11
14
|
|
|
12
15
|
program
|
|
13
16
|
.command('config')
|
|
14
17
|
.description('Configurar provedor e chave de API')
|
|
15
18
|
.action(configure);
|
|
16
19
|
|
|
17
|
-
program.action(startInteractive);
|
|
20
|
+
program.action(startInteractive);
|
|
18
21
|
|
|
19
|
-
program.parse();
|
|
22
|
+
program.parse();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bimmo-cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "🌿
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "🌿 Plataforma de Orquestração de Agentes IA (Swarms) com interface verde/lavanda, modo Auto-Edit e contexto inteligente.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bimmo": "bin/bimmo"
|
|
7
7
|
},
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
"grok",
|
|
16
16
|
"deepseek",
|
|
17
17
|
"openrouter",
|
|
18
|
+
"zai",
|
|
18
19
|
"agent",
|
|
20
|
+
"swarm",
|
|
21
|
+
"orchestration",
|
|
19
22
|
"multimodal",
|
|
20
23
|
"terminal"
|
|
21
24
|
],
|
package/src/config.js
CHANGED
|
@@ -29,6 +29,7 @@ export async function configure() {
|
|
|
29
29
|
choices: [
|
|
30
30
|
{ name: 'Criar novo perfil de IA', value: 'create' },
|
|
31
31
|
{ name: 'Selecionar perfil ativo', value: 'select' },
|
|
32
|
+
{ name: 'Gerenciar Agentes Especialistas', value: 'agents' },
|
|
32
33
|
{ name: 'Configurar chave Tavily', value: 'tavily' },
|
|
33
34
|
{ name: 'Sair', value: 'exit' }
|
|
34
35
|
]
|
|
@@ -37,6 +38,8 @@ export async function configure() {
|
|
|
37
38
|
|
|
38
39
|
if (action === 'exit') return;
|
|
39
40
|
|
|
41
|
+
if (action === 'agents') return configureAgents();
|
|
42
|
+
|
|
40
43
|
if (action === 'tavily') {
|
|
41
44
|
const { tavilyKey } = await inquirer.prompt([{
|
|
42
45
|
type: 'input',
|
|
@@ -74,7 +77,7 @@ export async function configure() {
|
|
|
74
77
|
{
|
|
75
78
|
type: 'input',
|
|
76
79
|
name: 'profileName',
|
|
77
|
-
message: 'Dê um nome para este perfil
|
|
80
|
+
message: 'Dê um nome para este perfil:',
|
|
78
81
|
validate: i => i.length > 0 || 'Nome obrigatório'
|
|
79
82
|
},
|
|
80
83
|
{
|
|
@@ -120,6 +123,50 @@ export async function configure() {
|
|
|
120
123
|
console.log(chalk.green(`\n✅ Perfil "${answers.profileName}" criado e ativado!`));
|
|
121
124
|
}
|
|
122
125
|
|
|
126
|
+
async function configureAgents() {
|
|
127
|
+
const agents = config.get('agents') || {};
|
|
128
|
+
const profiles = config.get('profiles') || {};
|
|
129
|
+
const profileList = Object.keys(profiles);
|
|
130
|
+
|
|
131
|
+
const { action } = await inquirer.prompt([{
|
|
132
|
+
type: 'list',
|
|
133
|
+
name: 'action',
|
|
134
|
+
message: 'Gerenciar Agentes:',
|
|
135
|
+
choices: ['Criar Agente', 'Listar Agentes', 'Remover Agente', 'Voltar']
|
|
136
|
+
}]);
|
|
137
|
+
|
|
138
|
+
if (action === 'Voltar') return configure();
|
|
139
|
+
|
|
140
|
+
if (action === 'Criar Agente') {
|
|
141
|
+
if (profileList.length === 0) {
|
|
142
|
+
console.log(chalk.red('Crie um perfil de IA primeiro.'));
|
|
143
|
+
return configure();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const answers = await inquirer.prompt([
|
|
147
|
+
{ type: 'input', name: 'name', message: 'Nome do Agente (ex: Arquiteto, Revisor):' },
|
|
148
|
+
{ type: 'list', name: 'profile', message: 'Qual perfil este agente usará?', choices: profileList },
|
|
149
|
+
{ type: 'input', name: 'modelOverride', message: 'Sobrescrever modelo (vazio para manter o do perfil):' },
|
|
150
|
+
{ type: 'list', name: 'mode', message: 'Modo padrão:', choices: ['chat', 'plan', 'edit'] },
|
|
151
|
+
{ type: 'editor', name: 'role', message: 'Descreva a Task/Papel deste agente (System Prompt):' }
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
agents[answers.name] = answers;
|
|
155
|
+
config.set('agents', agents);
|
|
156
|
+
console.log(chalk.green(`✓ Agente "${answers.name}" criado!`));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (action === 'Listar Agentes') {
|
|
160
|
+
console.log(chalk.blue('\nAgentes Configurados:'));
|
|
161
|
+
Object.keys(agents).forEach(name => {
|
|
162
|
+
console.log(`- ${chalk.bold(name)} [${agents[name].profile}] (Modo: ${agents[name].mode})`);
|
|
163
|
+
});
|
|
164
|
+
console.log('');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return configureAgents();
|
|
168
|
+
}
|
|
169
|
+
|
|
123
170
|
export function getConfig() {
|
|
124
171
|
return config.store;
|
|
125
172
|
}
|
package/src/interface.js
CHANGED
|
@@ -7,10 +7,12 @@ import ora from 'ora';
|
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import mime from 'mime-types';
|
|
10
|
+
import readline from 'readline';
|
|
10
11
|
|
|
11
12
|
import { getConfig, configure, updateActiveModel, switchProfile } from './config.js';
|
|
12
13
|
import { createProvider } from './providers/factory.js';
|
|
13
14
|
import { getProjectContext } from './project-context.js';
|
|
15
|
+
import { SwarmOrchestrator } from './orchestrator.js';
|
|
14
16
|
|
|
15
17
|
marked.use(new TerminalRenderer({
|
|
16
18
|
heading: chalk.hex('#c084fc').bold,
|
|
@@ -109,9 +111,9 @@ export async function startInteractive() {
|
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
let provider = createProvider(config);
|
|
114
|
+
const orchestrator = new SwarmOrchestrator(config);
|
|
112
115
|
const messages = [];
|
|
113
116
|
|
|
114
|
-
// 1. Injeta o sistema de contexto inteligente do projeto
|
|
115
117
|
const projectContext = getProjectContext();
|
|
116
118
|
messages.push({
|
|
117
119
|
role: 'system',
|
|
@@ -123,11 +125,14 @@ export async function startInteractive() {
|
|
|
123
125
|
console.log(lavender('─'.repeat(60)));
|
|
124
126
|
console.log(green(` Perfil Ativo: ${bold(config.activeProfile || 'Padrão')} (${config.provider.toUpperCase()})`));
|
|
125
127
|
console.log(green(` Modelo: ${bold(config.model)}`));
|
|
126
|
-
console.log(gray(' /chat | /plan | /edit | /switch [perfil] | /model [novo] | /help'));
|
|
128
|
+
console.log(gray(' /chat | /plan | /edit | /swarm | /switch [perfil] | /model [novo] | /help'));
|
|
127
129
|
console.log(lavender('─'.repeat(60)) + '\n');
|
|
128
130
|
|
|
129
131
|
console.log(lavender('👋 Olá! Sou seu agente BIMMO. No que posso atuar?\n'));
|
|
130
132
|
|
|
133
|
+
readline.emitKeypressEvents(process.stdin);
|
|
134
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
135
|
+
|
|
131
136
|
while (true) {
|
|
132
137
|
const modeIndicator = getModeStyle();
|
|
133
138
|
const { input } = await inquirer.prompt([
|
|
@@ -144,19 +149,18 @@ export async function startInteractive() {
|
|
|
144
149
|
|
|
145
150
|
if (cmd === '/exit' || cmd === 'exit' || cmd === 'sair') {
|
|
146
151
|
console.log(lavender('\n👋 BIMMO encerrando sessão. Até logo!\n'));
|
|
147
|
-
|
|
152
|
+
process.exit(0);
|
|
148
153
|
}
|
|
149
154
|
|
|
150
155
|
if (cmd === '/chat') { currentMode = 'chat'; console.log(lavender('✓ Modo CHAT.\n')); continue; }
|
|
151
156
|
if (cmd === '/plan') { currentMode = 'plan'; console.log(yellow('✓ Modo PLAN.\n')); continue; }
|
|
152
157
|
if (cmd === '/edit') { currentMode = 'edit'; console.log(chalk.red('⚠️ Modo EDIT.\n')); continue; }
|
|
153
158
|
|
|
154
|
-
// /switch [perfil] -> Troca Perfil + Chave + Provedor + Modelo instantaneamente
|
|
155
159
|
if (cmd.startsWith('/switch ')) {
|
|
156
160
|
const profileName = rawInput.split(' ')[1];
|
|
157
161
|
if (profileName && switchProfile(profileName)) {
|
|
158
|
-
config = getConfig();
|
|
159
|
-
provider = createProvider(config);
|
|
162
|
+
config = getConfig();
|
|
163
|
+
provider = createProvider(config);
|
|
160
164
|
console.log(green(`\n✓ Trocado para o perfil "${bold(profileName)}"!`));
|
|
161
165
|
console.log(gray(` IA: ${config.provider.toUpperCase()} | Modelo: ${config.model}\n`));
|
|
162
166
|
} else {
|
|
@@ -165,7 +169,6 @@ export async function startInteractive() {
|
|
|
165
169
|
continue;
|
|
166
170
|
}
|
|
167
171
|
|
|
168
|
-
// /model [modelo] -> Troca apenas o modelo do Perfil atual
|
|
169
172
|
if (cmd.startsWith('/model ')) {
|
|
170
173
|
const newModel = rawInput.split(' ')[1];
|
|
171
174
|
if (newModel) {
|
|
@@ -189,56 +192,83 @@ export async function startInteractive() {
|
|
|
189
192
|
console.log(gray(`
|
|
190
193
|
Comandos Disponíveis:
|
|
191
194
|
/chat /plan /edit → Mudar modo de operação
|
|
192
|
-
/switch [nome] → Mudar PERFIL (
|
|
193
|
-
/model [nome] → Mudar apenas o MODELO
|
|
194
|
-
/
|
|
195
|
-
/config → Gerenciar perfis e
|
|
196
|
-
/
|
|
195
|
+
/switch [nome] → Mudar PERFIL (IA completa)
|
|
196
|
+
/model [nome] → Mudar apenas o MODELO atual
|
|
197
|
+
/swarm → Configurar e rodar enxames de agentes
|
|
198
|
+
/config → Gerenciar perfis e agentes
|
|
199
|
+
/init → Inicializar .bimmorc.json
|
|
197
200
|
@caminho → Anexar arquivos ou imagens
|
|
198
201
|
`));
|
|
199
202
|
continue;
|
|
200
203
|
}
|
|
201
204
|
|
|
202
|
-
if (cmd === '/
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
205
|
+
if (cmd === '/config') { await configure(); config = getConfig(); provider = createProvider(config); continue; }
|
|
206
|
+
|
|
207
|
+
if (cmd === '/swarm') {
|
|
208
|
+
const agents = config.agents || {};
|
|
209
|
+
const agentList = Object.keys(agents);
|
|
210
|
+
|
|
211
|
+
if (agentList.length < 2) {
|
|
212
|
+
console.log(chalk.yellow('\nVocê precisa de pelo menos 2 Agentes configurados para rodar um Enxame.\nUse /config para criar Agentes.\n'));
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const { swarmAction } = await inquirer.prompt([{
|
|
217
|
+
type: 'list',
|
|
218
|
+
name: 'swarmAction',
|
|
219
|
+
message: 'Ação de Enxame:',
|
|
220
|
+
choices: ['Rodar Enxame Sequencial', 'Rodar Enxame Hierárquico', 'Voltar']
|
|
221
|
+
}]);
|
|
222
|
+
|
|
223
|
+
if (swarmAction === 'Voltar') continue;
|
|
224
|
+
|
|
225
|
+
const { goal } = await inquirer.prompt([{ type: 'input', name: 'goal', message: 'Qual o objetivo final deste enxame?' }]);
|
|
226
|
+
|
|
227
|
+
if (swarmAction === 'Rodar Enxame Sequencial') {
|
|
228
|
+
const { selectedAgents } = await inquirer.prompt([{
|
|
229
|
+
type: 'checkbox',
|
|
230
|
+
name: 'selectedAgents',
|
|
231
|
+
message: 'Selecione os agentes e a ordem (mínimo 2):',
|
|
232
|
+
choices: agentList
|
|
210
233
|
}]);
|
|
211
|
-
|
|
234
|
+
|
|
235
|
+
if (selectedAgents.length < 2) {
|
|
236
|
+
console.log(chalk.red('\nSelecione pelo menos 2 agentes.\n'));
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const finalResult = await orchestrator.runSequential(selectedAgents, goal);
|
|
242
|
+
console.log(lavender('\n=== RESULTADO FINAL DO ENXAME ===\n'));
|
|
243
|
+
console.log(marked(finalResult));
|
|
244
|
+
} catch (e) {
|
|
245
|
+
console.error(chalk.red(`\nErro no Enxame: ${e.message}`));
|
|
246
|
+
}
|
|
212
247
|
}
|
|
213
248
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
fs.writeFileSync(bimmoRcPath, JSON.stringify(initialConfig, null, 2));
|
|
227
|
-
console.log(green(`\n✅ Arquivo .bimmorc.json criado com sucesso em: ${bold(bimmoRcPath)}\n`));
|
|
228
|
-
|
|
229
|
-
// Recarrega o contexto para a conversa atual
|
|
230
|
-
messages.push({
|
|
231
|
-
role: 'system',
|
|
232
|
-
content: `Novo contexto inicializado via /init:\n${JSON.stringify(initialConfig, null, 2)}`
|
|
233
|
-
});
|
|
249
|
+
if (swarmAction === 'Rodar Enxame Hierárquico') {
|
|
250
|
+
const { manager } = await inquirer.prompt([{ type: 'list', name: 'manager', message: 'Selecione o Agente Líder (Manager):', choices: agentList }]);
|
|
251
|
+
const { workers } = await inquirer.prompt([{ type: 'checkbox', name: 'workers', message: 'Selecione os Workers:', choices: agentList.filter(a => a !== manager) }]);
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const finalResult = await orchestrator.runHierarchical(manager, workers, goal);
|
|
255
|
+
console.log(lavender('\n=== RESULTADO FINAL DO ENXAME ===\n'));
|
|
256
|
+
console.log(marked(finalResult));
|
|
257
|
+
} catch (e) {
|
|
258
|
+
console.error(chalk.red(`\nErro no Enxame: ${e.message}`));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
234
261
|
continue;
|
|
235
262
|
}
|
|
236
263
|
|
|
237
|
-
if (cmd === '/config') { await configure(); config = getConfig(); provider = createProvider(config); continue; }
|
|
238
|
-
|
|
239
264
|
if (rawInput === '') continue;
|
|
240
265
|
|
|
241
|
-
|
|
266
|
+
const controller = new AbortController();
|
|
267
|
+
const interruptHandler = () => controller.abort();
|
|
268
|
+
const keypressHandler = (str, key) => { if (key.name === 'escape' || (key.ctrl && key.name === 'c')) interruptHandler(); };
|
|
269
|
+
process.on('SIGINT', interruptHandler);
|
|
270
|
+
process.stdin.on('keypress', keypressHandler);
|
|
271
|
+
|
|
242
272
|
let modeInstr = "";
|
|
243
273
|
if (currentMode === 'plan') modeInstr = "\n[MODO PLAN] Descreva e analise, mas NÃO altere arquivos.";
|
|
244
274
|
else if (currentMode === 'edit') modeInstr = "\n[MODO EDIT] Você tem permissão para usar write_file e run_command AGORA.";
|
|
@@ -250,22 +280,30 @@ Comandos Disponíveis:
|
|
|
250
280
|
});
|
|
251
281
|
|
|
252
282
|
const spinner = ora({
|
|
253
|
-
text: lavender(`bimmo (${currentMode}) pensando
|
|
283
|
+
text: lavender(`bimmo (${currentMode}) pensando... (Ctrl+C para interromper)`),
|
|
254
284
|
color: currentMode === 'edit' ? 'red' : 'magenta'
|
|
255
285
|
}).start();
|
|
256
286
|
|
|
257
287
|
try {
|
|
258
|
-
|
|
288
|
+
let responseText = await provider.sendMessage(messages, { signal: controller.signal });
|
|
259
289
|
spinner.stop();
|
|
290
|
+
const cleanedText = responseText.replace(/<\/?[^>]+(>|$)/g, "");
|
|
260
291
|
messages.push({ role: 'assistant', content: responseText });
|
|
261
|
-
|
|
262
292
|
console.log('\n' + lavender('bimmo') + getModeStyle());
|
|
263
293
|
console.log(lavender('─'.repeat(50)));
|
|
264
|
-
console.log(marked(
|
|
294
|
+
console.log(marked(cleanedText));
|
|
265
295
|
console.log(gray('─'.repeat(50)) + '\n');
|
|
266
296
|
} catch (err) {
|
|
267
297
|
spinner.stop();
|
|
268
|
-
|
|
298
|
+
if (controller.signal.aborted || err.name === 'AbortError') {
|
|
299
|
+
console.log(yellow('\n\n⚠️ Operação interrompida pelo usuário.\n'));
|
|
300
|
+
messages.pop();
|
|
301
|
+
} else {
|
|
302
|
+
console.error(chalk.red('\n✖ Erro Crítico:') + ' ' + err.message + '\n');
|
|
303
|
+
}
|
|
304
|
+
} finally {
|
|
305
|
+
process.off('SIGINT', interruptHandler);
|
|
306
|
+
process.stdin.off('keypress', keypressHandler);
|
|
269
307
|
}
|
|
270
308
|
}
|
|
271
309
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { createProvider } from './providers/factory.js';
|
|
4
|
+
import { getProjectContext } from './project-context.js';
|
|
5
|
+
|
|
6
|
+
export class SwarmOrchestrator {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.agents = config.agents || {};
|
|
10
|
+
this.profiles = config.profiles || {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Executa uma tarefa sequencialmente através de uma lista de agentes.
|
|
15
|
+
* O output de um agente vira o contexto do próximo.
|
|
16
|
+
*/
|
|
17
|
+
async runSequential(agentNames, goal, options = {}) {
|
|
18
|
+
let currentContext = goal;
|
|
19
|
+
const results = [];
|
|
20
|
+
|
|
21
|
+
console.log(chalk.cyan(`\n🚀 Iniciando Enxame Sequencial: ${agentNames.join(' → ')}\n`));
|
|
22
|
+
|
|
23
|
+
for (const name of agentNames) {
|
|
24
|
+
const agent = this.agents[name];
|
|
25
|
+
if (!agent) throw new Error(`Agente ${name} não encontrado.`);
|
|
26
|
+
|
|
27
|
+
const profile = this.profiles[agent.profile];
|
|
28
|
+
if (!profile) throw new Error(`Perfil ${agent.profile} do agente ${name} não encontrado.`);
|
|
29
|
+
|
|
30
|
+
const agentConfig = {
|
|
31
|
+
...profile,
|
|
32
|
+
model: agent.modelOverride || profile.model
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const provider = createProvider(agentConfig);
|
|
36
|
+
const spinner = ora({
|
|
37
|
+
text: chalk.magenta(`Agente [${name}] trabalhando...`),
|
|
38
|
+
color: agent.mode === 'edit' ? 'red' : 'magenta'
|
|
39
|
+
}).start();
|
|
40
|
+
|
|
41
|
+
const messages = [
|
|
42
|
+
{ role: 'system', content: `Você é o agente ${name}. Sua tarefa específica é: ${agent.role}\n\nMODO ATUAL: ${agent.mode.toUpperCase()}\n${getProjectContext()}` },
|
|
43
|
+
{ role: 'user', content: `CONTEXTO ATUAL:\n${currentContext}\n\nOBJETIVO DO ENXAME:\n${goal}\n\nPor favor, execute sua parte e retorne o resultado final processado.` }
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const response = await provider.sendMessage(messages, { signal: options.signal });
|
|
48
|
+
spinner.succeed(chalk.green(`Agente [${name}] concluído.`));
|
|
49
|
+
|
|
50
|
+
currentContext = response;
|
|
51
|
+
results.push({ agent: name, output: response });
|
|
52
|
+
} catch (err) {
|
|
53
|
+
spinner.fail(chalk.red(`Agente [${name}] falhou: ${err.message}`));
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return currentContext;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Executa uma tarefa hierárquica.
|
|
63
|
+
* Um Manager recebe o objetivo, define o que cada Worker deve fazer, e consolida.
|
|
64
|
+
*/
|
|
65
|
+
async runHierarchical(managerName, workerNames, goal, options = {}) {
|
|
66
|
+
console.log(chalk.cyan(`\n👑 Iniciando Enxame Hierárquico (Líder: ${managerName})\n`));
|
|
67
|
+
|
|
68
|
+
// Passo 1: Manager analisa e delega
|
|
69
|
+
const managerOutput = await this.runSequential([managerName], `Analise o objetivo abaixo e descreva o que cada um dos seguintes agentes deve fazer: ${workerNames.join(', ')}.\n\nOBJETIVO: ${goal}`);
|
|
70
|
+
|
|
71
|
+
// Passo 2: Workers executam baseado na delegação do Manager (em paralelo para velocidade)
|
|
72
|
+
console.log(chalk.blue(`\n👷 Workers entrando em ação...\n`));
|
|
73
|
+
const workerPromises = workerNames.map(name =>
|
|
74
|
+
this.runSequential([name], `Baseado no plano do Manager:\n${managerOutput}\n\nExecute sua tarefa para o objetivo: ${goal}`, options)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const workerResults = await Promise.all(workerPromises);
|
|
78
|
+
|
|
79
|
+
// Passo 3: Manager consolida tudo
|
|
80
|
+
const finalResult = await this.runSequential([managerName], `Aqui estão os resultados dos workers:\n${workerResults.join('\n---\n')}\n\nPor favor, consolide tudo em um resultado final perfeito para o objetivo: ${goal}`, options);
|
|
81
|
+
|
|
82
|
+
return finalResult;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -21,7 +21,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
async sendMessage(messages) {
|
|
24
|
+
async sendMessage(messages, options = {}) {
|
|
25
25
|
const systemMessage = messages.find(m => m.role === 'system');
|
|
26
26
|
const userMessages = messages
|
|
27
27
|
.filter(m => m.role !== 'system')
|
|
@@ -30,7 +30,6 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
30
30
|
content: this.formatContent(m.content)
|
|
31
31
|
}));
|
|
32
32
|
|
|
33
|
-
// Converte tools do agent.js para o formato da Anthropic
|
|
34
33
|
const anthropicTools = tools.map(t => ({
|
|
35
34
|
name: t.name,
|
|
36
35
|
description: t.description,
|
|
@@ -44,17 +43,18 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
44
43
|
messages: userMessages,
|
|
45
44
|
tools: anthropicTools,
|
|
46
45
|
temperature: 0.7
|
|
47
|
-
});
|
|
46
|
+
}, { signal: options.signal });
|
|
48
47
|
|
|
49
48
|
if (response.stop_reason === 'tool_use') {
|
|
50
49
|
const toolUse = response.content.find(p => p.type === 'tool_use');
|
|
51
50
|
const tool = tools.find(t => t.name === toolUse.name);
|
|
52
51
|
|
|
53
52
|
if (tool) {
|
|
53
|
+
if (options.signal?.aborted) throw new Error('Abortado pelo usuário');
|
|
54
|
+
|
|
54
55
|
console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
|
|
55
56
|
const result = await tool.execute(toolUse.input);
|
|
56
57
|
|
|
57
|
-
// Adiciona a resposta da IA e o resultado da tool ao histórico
|
|
58
58
|
const nextMessages = [
|
|
59
59
|
...messages,
|
|
60
60
|
{ role: 'assistant', content: response.content },
|
|
@@ -68,10 +68,10 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
68
68
|
}
|
|
69
69
|
];
|
|
70
70
|
|
|
71
|
-
return this.sendMessage(nextMessages);
|
|
71
|
+
return this.sendMessage(nextMessages, options);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
return response.content[0].text;
|
|
76
76
|
}
|
|
77
|
-
}
|
|
77
|
+
}
|
package/src/providers/base.js
CHANGED
package/src/providers/gemini.js
CHANGED
|
@@ -18,7 +18,7 @@ export class GeminiProvider extends BaseProvider {
|
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
async sendMessage(messages) {
|
|
21
|
+
async sendMessage(messages, options = {}) {
|
|
22
22
|
const systemPrompt = messages.find(m => m.role === 'system')?.content;
|
|
23
23
|
const history = messages
|
|
24
24
|
.filter(m => m.role !== 'system')
|
|
@@ -30,7 +30,6 @@ export class GeminiProvider extends BaseProvider {
|
|
|
30
30
|
|
|
31
31
|
const lastMessageContent = this.formatContent(messages[messages.length - 1].content);
|
|
32
32
|
|
|
33
|
-
// Converte tools do agent.js para o formato do Gemini
|
|
34
33
|
const geminiTools = tools.map(t => ({
|
|
35
34
|
functionDeclarations: [{
|
|
36
35
|
name: t.name,
|
|
@@ -53,19 +52,19 @@ export class GeminiProvider extends BaseProvider {
|
|
|
53
52
|
const result = await chat.sendMessage(lastMessageContent);
|
|
54
53
|
const response = await result.response;
|
|
55
54
|
|
|
56
|
-
// Processamento de Tool Calls no Gemini
|
|
57
55
|
const call = response.candidates[0].content.parts.find(p => p.functionCall);
|
|
58
56
|
if (call) {
|
|
57
|
+
if (options.signal?.aborted) throw new Error('Abortado pelo usuário');
|
|
58
|
+
|
|
59
59
|
const tool = tools.find(t => t.name === call.functionCall.name);
|
|
60
60
|
if (tool) {
|
|
61
61
|
console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
|
|
62
|
-
const
|
|
62
|
+
const toolResult = await tool.execute(call.functionCall.args);
|
|
63
63
|
|
|
64
|
-
// No Gemini, enviamos o resultado de volta para o chat
|
|
65
64
|
const resultResponse = await chat.sendMessage([{
|
|
66
65
|
functionResponse: {
|
|
67
66
|
name: call.functionCall.name,
|
|
68
|
-
response: { content:
|
|
67
|
+
response: { content: toolResult }
|
|
69
68
|
}
|
|
70
69
|
}]);
|
|
71
70
|
return resultResponse.response.text();
|
|
@@ -74,4 +73,4 @@ export class GeminiProvider extends BaseProvider {
|
|
|
74
73
|
|
|
75
74
|
return response.text();
|
|
76
75
|
}
|
|
77
|
-
}
|
|
76
|
+
}
|
package/src/providers/grok.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
2
|
import { BaseProvider } from './base.js';
|
|
3
|
+
import { tools } from '../agent.js';
|
|
3
4
|
|
|
4
5
|
export class GrokProvider extends BaseProvider {
|
|
5
6
|
constructor(config) {
|
|
@@ -33,15 +34,15 @@ export class GrokProvider extends BaseProvider {
|
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
async sendMessage(messages) {
|
|
37
|
+
async sendMessage(messages, options = {}) {
|
|
37
38
|
const formattedMessages = this.formatMessages(messages);
|
|
38
39
|
|
|
39
40
|
const response = await this.client.chat.completions.create({
|
|
40
41
|
model: this.config.model,
|
|
41
42
|
messages: formattedMessages,
|
|
42
43
|
temperature: 0.7
|
|
43
|
-
});
|
|
44
|
+
}, { signal: options.signal });
|
|
44
45
|
|
|
45
46
|
return response.choices[0].message.content;
|
|
46
47
|
}
|
|
47
|
-
}
|
|
48
|
+
}
|
package/src/providers/ollama.js
CHANGED
|
@@ -13,7 +13,7 @@ export class OllamaProvider extends BaseProvider {
|
|
|
13
13
|
|
|
14
14
|
const images = msg.content
|
|
15
15
|
.filter(part => part.type === 'image')
|
|
16
|
-
.map(part => part.data);
|
|
16
|
+
.map(part => part.data);
|
|
17
17
|
|
|
18
18
|
const text = msg.content
|
|
19
19
|
.filter(part => part.type === 'text')
|
|
@@ -28,7 +28,7 @@ export class OllamaProvider extends BaseProvider {
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async sendMessage(messages) {
|
|
31
|
+
async sendMessage(messages, options = {}) {
|
|
32
32
|
const formattedMessages = this.formatMessages(messages);
|
|
33
33
|
|
|
34
34
|
const response = await this.client.chat({
|
|
@@ -38,7 +38,7 @@ export class OllamaProvider extends BaseProvider {
|
|
|
38
38
|
options: {
|
|
39
39
|
temperature: 0.7,
|
|
40
40
|
}
|
|
41
|
-
});
|
|
41
|
+
}, { signal: options.signal });
|
|
42
42
|
|
|
43
43
|
const text = response.message?.content;
|
|
44
44
|
|
|
@@ -48,4 +48,4 @@ export class OllamaProvider extends BaseProvider {
|
|
|
48
48
|
|
|
49
49
|
return text;
|
|
50
50
|
}
|
|
51
|
-
}
|
|
51
|
+
}
|
package/src/providers/openai.js
CHANGED
|
@@ -34,7 +34,7 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
async sendMessage(messages) {
|
|
37
|
+
async sendMessage(messages, options = {}) {
|
|
38
38
|
const formattedMessages = this.formatMessages(messages);
|
|
39
39
|
|
|
40
40
|
const openAiTools = tools.map(t => ({
|
|
@@ -46,25 +46,26 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
46
46
|
}
|
|
47
47
|
}));
|
|
48
48
|
|
|
49
|
-
const
|
|
49
|
+
const requestOptions = {
|
|
50
50
|
model: this.config.model,
|
|
51
51
|
messages: formattedMessages,
|
|
52
52
|
temperature: 0.7
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
// Só envia tools se houver alguma definida para evitar erros em alguns provedores
|
|
56
55
|
if (openAiTools.length > 0) {
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
requestOptions.tools = openAiTools;
|
|
57
|
+
requestOptions.tool_choice = 'auto';
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
const response = await this.client.chat.completions.create(options);
|
|
60
|
+
const response = await this.client.chat.completions.create(requestOptions, { signal: options.signal });
|
|
62
61
|
|
|
63
62
|
const message = response.choices[0].message;
|
|
64
63
|
|
|
65
64
|
if (message.tool_calls) {
|
|
66
65
|
const toolResults = [];
|
|
67
66
|
for (const toolCall of message.tool_calls) {
|
|
67
|
+
if (options.signal?.aborted) throw new Error('Abortado pelo usuário');
|
|
68
|
+
|
|
68
69
|
const tool = tools.find(t => t.name === toolCall.function.name);
|
|
69
70
|
if (tool) {
|
|
70
71
|
console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
|
|
@@ -80,9 +81,9 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
const nextMessages = [...formattedMessages, message, ...toolResults];
|
|
83
|
-
return this.sendMessage(nextMessages);
|
|
84
|
+
return this.sendMessage(nextMessages, options);
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
return message.content;
|
|
87
88
|
}
|
|
88
|
-
}
|
|
89
|
+
}
|