bimmo-cli 2.2.5 → 2.2.9
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/package.json +3 -2
- package/src/agent.js +16 -9
- package/src/interface.js +93 -111
- package/src/providers/anthropic.js +1 -2
- package/src/providers/gemini.js +1 -1
- package/src/providers/openai.js +2 -8
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bimmo-cli",
|
|
3
|
-
"version": "2.2.
|
|
4
|
-
"description": "🌿 Plataforma de IA universal profissional com Agentes e Swarms. Suporte a Autocomplete, Diffs e Contexto Inteligente.",
|
|
3
|
+
"version": "2.2.9",
|
|
4
|
+
"description": "🌿 Plataforma de IA universal profissional com Agentes e Swarms. Suporte a Autocomplete real-time (estilo gemini-cli), Diffs e Contexto Inteligente.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bimmo": "bin/bimmo"
|
|
7
7
|
},
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"conf": "^13.0.0",
|
|
30
30
|
"diff": "^7.0.0",
|
|
31
31
|
"figlet": "^1.7.0",
|
|
32
|
+
"fuzzy": "^0.1.3",
|
|
32
33
|
"inquirer": "^9.3.8",
|
|
33
34
|
"marked": "^14.0.0",
|
|
34
35
|
"marked-terminal": "^7.0.0",
|
package/src/agent.js
CHANGED
|
@@ -28,6 +28,7 @@ export const tools = [
|
|
|
28
28
|
},
|
|
29
29
|
execute: async ({ query }) => {
|
|
30
30
|
if (!tvly) return 'Erro: Chave de API da Tavily não configurada. Use /config para configurar.';
|
|
31
|
+
console.log(chalk.blue(`\n 🌐 Pesquisando na web: ${chalk.bold(query)}...`));
|
|
31
32
|
const searchResponse = await tvly.search(query, {
|
|
32
33
|
searchDepth: 'advanced',
|
|
33
34
|
maxResults: 5
|
|
@@ -51,6 +52,7 @@ export const tools = [
|
|
|
51
52
|
},
|
|
52
53
|
execute: async ({ path: filePath }) => {
|
|
53
54
|
try {
|
|
55
|
+
console.log(chalk.blue(`\n 📖 Lendo arquivo: ${chalk.bold(filePath)}...`));
|
|
54
56
|
return fs.readFileSync(filePath, 'utf-8');
|
|
55
57
|
} catch (err) {
|
|
56
58
|
return `Erro ao ler arquivo: ${err.message}`;
|
|
@@ -77,7 +79,7 @@ export const tools = [
|
|
|
77
79
|
const differences = diff.diffLines(oldContent, content);
|
|
78
80
|
|
|
79
81
|
console.log(`\n${chalk.cyan('📝 Alterações propostas em:')} ${chalk.bold(filePath)}`);
|
|
80
|
-
console.log(chalk.gray('─'.repeat(
|
|
82
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
81
83
|
|
|
82
84
|
let hasChanges = false;
|
|
83
85
|
differences.forEach((part) => {
|
|
@@ -85,20 +87,25 @@ export const tools = [
|
|
|
85
87
|
const color = part.added ? chalk.green : part.removed ? chalk.red : chalk.gray;
|
|
86
88
|
const prefix = part.added ? '+' : part.removed ? '-' : ' ';
|
|
87
89
|
|
|
88
|
-
// Mostra apenas linhas com mudanças ou um pouco de contexto
|
|
89
90
|
if (part.added || part.removed) {
|
|
90
|
-
|
|
91
|
+
// Garante que cada linha tenha o prefixo
|
|
92
|
+
const lines = part.value.split('\n');
|
|
93
|
+
lines.forEach(line => {
|
|
94
|
+
if (line || part.value.endsWith('\n')) {
|
|
95
|
+
process.stdout.write(color(`${prefix} ${line}\n`));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
91
98
|
} else {
|
|
92
99
|
// Mostra apenas as primeiras e últimas linhas de blocos sem mudança para encurtar
|
|
93
|
-
const lines = part.value.split('\n');
|
|
100
|
+
const lines = part.value.split('\n').filter(l => l.trim() !== "");
|
|
94
101
|
if (lines.length > 4) {
|
|
95
|
-
process.stdout.write(color(` ${lines[0]}\n ...\n ${lines[lines.length-
|
|
96
|
-
} else {
|
|
97
|
-
process.stdout.write(color(` ${
|
|
102
|
+
process.stdout.write(color(` ${lines[0]}\n ...\n ${lines[lines.length-1]}\n`));
|
|
103
|
+
} else if (lines.length > 0) {
|
|
104
|
+
lines.forEach(line => process.stdout.write(color(` ${line}\n`)));
|
|
98
105
|
}
|
|
99
106
|
}
|
|
100
107
|
});
|
|
101
|
-
console.log(chalk.gray('
|
|
108
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
102
109
|
|
|
103
110
|
if (!hasChanges) {
|
|
104
111
|
return "Nenhuma mudança detectada no arquivo.";
|
|
@@ -143,7 +150,7 @@ export const tools = [
|
|
|
143
150
|
},
|
|
144
151
|
execute: async ({ command }) => {
|
|
145
152
|
try {
|
|
146
|
-
console.log(
|
|
153
|
+
console.log(chalk.yellow(`\n ⚡ Comando proposto: ${chalk.bold(command)}`));
|
|
147
154
|
|
|
148
155
|
if (!editState.autoAccept) {
|
|
149
156
|
const { approve } = await inquirer.prompt([{
|
package/src/interface.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import figlet from 'figlet';
|
|
3
|
-
import inquirer from 'inquirer';
|
|
4
3
|
import { marked } from 'marked';
|
|
5
4
|
import TerminalRenderer from 'marked-terminal';
|
|
6
5
|
import ora from 'ora';
|
|
@@ -9,6 +8,7 @@ import path from 'path';
|
|
|
9
8
|
import mime from 'mime-types';
|
|
10
9
|
import readline from 'readline';
|
|
11
10
|
import { fileURLToPath } from 'url';
|
|
11
|
+
import { stripVTControlCharacters } from 'util';
|
|
12
12
|
|
|
13
13
|
import { getConfig, configure, updateActiveModel, switchProfile } from './config.js';
|
|
14
14
|
import { createProvider } from './providers/factory.js';
|
|
@@ -21,12 +21,13 @@ const __dirname = path.dirname(__filename);
|
|
|
21
21
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
22
22
|
const version = pkg.version;
|
|
23
23
|
|
|
24
|
-
//
|
|
24
|
+
// Configuração do renderizador - DROP TOTAL DE HTML
|
|
25
25
|
const terminalRenderer = new TerminalRenderer({
|
|
26
26
|
heading: chalk.hex('#c084fc').bold,
|
|
27
27
|
code: chalk.hex('#00ff9d'),
|
|
28
28
|
strong: chalk.bold,
|
|
29
29
|
em: chalk.italic,
|
|
30
|
+
html: (html) => '', // Remove qualquer tag HTML que sobrar
|
|
30
31
|
});
|
|
31
32
|
|
|
32
33
|
marked.setOptions({ renderer: terminalRenderer });
|
|
@@ -42,40 +43,24 @@ let activePersona = null;
|
|
|
42
43
|
let exitCounter = 0;
|
|
43
44
|
let exitTimer = null;
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
'pt-BR': {
|
|
47
|
-
welcome: 'Olá! Estou pronto. No que posso ajudar?',
|
|
48
|
-
thinking: 'bimmo pensando...',
|
|
49
|
-
interrupted: 'Operação interrompida.',
|
|
50
|
-
exitHint: '(Pressione Ctrl+C novamente para sair)',
|
|
51
|
-
switchOk: 'Perfil ativado!',
|
|
52
|
-
agentOk: 'Agente ativado:',
|
|
53
|
-
modeEdit: 'Modo EDIT ativado.',
|
|
54
|
-
help: '\nComandos:\n /chat | /plan | /edit | /init\n /switch [nome] | /model [nome]\n /use [agente] | /use normal\n /config | /clear | @arquivo\n'
|
|
55
|
-
},
|
|
56
|
-
'en-US': {
|
|
57
|
-
welcome: 'Hello! I am ready. How can I help you?',
|
|
58
|
-
thinking: 'bimmo thinking...',
|
|
59
|
-
interrupted: 'Operation interrupted.',
|
|
60
|
-
exitHint: '(Press Ctrl+C again to exit)',
|
|
61
|
-
switchOk: 'Profile activated!',
|
|
62
|
-
agentOk: 'Agent activated:',
|
|
63
|
-
modeEdit: 'EDIT mode activated.',
|
|
64
|
-
help: '\nCommands:\n /chat | /plan | /edit | /init\n /switch [name] | /model [name]\n /use [agent] | /use normal\n /config | /clear | @file\n'
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
function getFilesForCompletion(partialPath) {
|
|
46
|
+
function getFilesForPreview(partialPath) {
|
|
69
47
|
try {
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
48
|
+
const p = partialPath.startsWith('@') ? partialPath.slice(1) : partialPath;
|
|
49
|
+
const dir = path.dirname(p) === '.' && !p.includes('/') ? '.' : path.dirname(p);
|
|
50
|
+
const base = path.basename(p) === '.' ? '' : path.basename(p);
|
|
51
|
+
|
|
52
|
+
const searchDir = path.resolve(process.cwd(), dir);
|
|
53
|
+
if (!fs.existsSync(searchDir)) return [];
|
|
54
|
+
|
|
55
|
+
const files = fs.readdirSync(searchDir);
|
|
73
56
|
return files
|
|
74
|
-
.filter(f => f.startsWith(base) && !f.startsWith('.') && f !== 'node_modules')
|
|
75
|
-
.map(f =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
57
|
+
.filter(f => (base === '' || f.startsWith(base)) && !f.startsWith('.') && f !== 'node_modules')
|
|
58
|
+
.map(f => {
|
|
59
|
+
const rel = path.join(dir === '.' ? '' : dir, f);
|
|
60
|
+
const isDir = fs.statSync(path.join(searchDir, f)).isDirectory();
|
|
61
|
+
return { name: rel, isDir };
|
|
62
|
+
});
|
|
63
|
+
} catch (e) { return []; }
|
|
79
64
|
}
|
|
80
65
|
|
|
81
66
|
function cleanAIResponse(text) {
|
|
@@ -90,9 +75,6 @@ function cleanAIResponse(text) {
|
|
|
90
75
|
|
|
91
76
|
export async function startInteractive() {
|
|
92
77
|
let config = getConfig();
|
|
93
|
-
const lang = config.language || 'pt-BR';
|
|
94
|
-
const t = i18n[lang] || i18n['pt-BR'];
|
|
95
|
-
|
|
96
78
|
if (!config.provider || !config.apiKey) {
|
|
97
79
|
console.log(lavender(figlet.textSync('bimmo')));
|
|
98
80
|
await configure();
|
|
@@ -121,37 +103,60 @@ export async function startInteractive() {
|
|
|
121
103
|
console.log(green(` Modelo: ${bold(config.model)}`));
|
|
122
104
|
console.log(lavender('─'.repeat(60)) + '\n');
|
|
123
105
|
|
|
124
|
-
console.log(lavender(`👋 ${t.welcome}\n`));
|
|
125
|
-
|
|
126
106
|
const rl = readline.createInterface({
|
|
127
107
|
input: process.stdin,
|
|
128
108
|
output: process.stdout,
|
|
129
109
|
terminal: true,
|
|
130
|
-
historySize: 100,
|
|
131
110
|
completer: (line) => {
|
|
132
111
|
const words = line.split(' ');
|
|
133
112
|
const lastWord = words[words.length - 1];
|
|
134
113
|
if (lastWord.startsWith('@')) {
|
|
135
|
-
const hits =
|
|
136
|
-
return [hits
|
|
114
|
+
const hits = getFilesForPreview(lastWord).map(h => `@${h.name}`);
|
|
115
|
+
return [hits, lastWord];
|
|
137
116
|
}
|
|
138
117
|
return [[], line];
|
|
139
118
|
}
|
|
140
119
|
});
|
|
141
120
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
121
|
+
let currentPreviewLines = 0;
|
|
122
|
+
|
|
123
|
+
const clearPreview = () => {
|
|
124
|
+
if (currentPreviewLines > 0) {
|
|
125
|
+
// Move o cursor para o final das linhas de preview e apaga tudo
|
|
126
|
+
readline.moveCursor(process.stdout, 0, currentPreviewLines);
|
|
127
|
+
for (let i = 0; i < currentPreviewLines; i++) {
|
|
128
|
+
readline.moveCursor(process.stdout, 0, -1);
|
|
129
|
+
readline.clearLine(process.stdout, 0);
|
|
130
|
+
}
|
|
131
|
+
currentPreviewLines = 0;
|
|
153
132
|
}
|
|
154
|
-
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const showPreview = () => {
|
|
136
|
+
clearPreview();
|
|
137
|
+
const words = rl.line.split(' ');
|
|
138
|
+
const lastWord = words[words.length - 1];
|
|
139
|
+
|
|
140
|
+
if (lastWord.startsWith('@')) {
|
|
141
|
+
const files = getFilesForPreview(lastWord);
|
|
142
|
+
if (files.length > 0) {
|
|
143
|
+
process.stdout.write('\n');
|
|
144
|
+
const displayFiles = files.slice(0, 8);
|
|
145
|
+
displayFiles.forEach(f => {
|
|
146
|
+
process.stdout.write(gray(` ${f.isDir ? '📁' : '📄'} ${f.name}\n`));
|
|
147
|
+
});
|
|
148
|
+
currentPreviewLines = displayFiles.length + 1;
|
|
149
|
+
// Volta o cursor para a linha do prompt de forma segura
|
|
150
|
+
readline.moveCursor(process.stdout, 0, -currentPreviewLines);
|
|
151
|
+
// Posiciona o cursor exatamente onde o usuário estava digitando
|
|
152
|
+
const promptText = rl.getPrompt();
|
|
153
|
+
const visualPromptLen = stripVTControlCharacters(promptText).length;
|
|
154
|
+
const totalLen = visualPromptLen + rl.line.length;
|
|
155
|
+
const cols = process.stdout.columns || 80;
|
|
156
|
+
readline.cursorTo(process.stdout, totalLen % cols);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
155
160
|
|
|
156
161
|
const displayPrompt = () => {
|
|
157
162
|
const personaLabel = activePersona ? `[${activePersona.toUpperCase()}]` : '';
|
|
@@ -163,108 +168,85 @@ export async function startInteractive() {
|
|
|
163
168
|
rl.prompt();
|
|
164
169
|
};
|
|
165
170
|
|
|
171
|
+
// Escuta teclas para o preview em tempo real
|
|
172
|
+
process.stdin.on('keypress', (s, key) => {
|
|
173
|
+
if (key && (key.name === 'return' || key.name === 'enter')) return;
|
|
174
|
+
setImmediate(() => showPreview());
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
rl.on('SIGINT', () => {
|
|
178
|
+
if (exitCounter === 0) {
|
|
179
|
+
exitCounter++;
|
|
180
|
+
process.stdout.write(`\n${gray('(Pressione novamente para sair)')}\n`);
|
|
181
|
+
exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
|
|
182
|
+
displayPrompt();
|
|
183
|
+
} else { process.exit(0); }
|
|
184
|
+
});
|
|
185
|
+
|
|
166
186
|
displayPrompt();
|
|
167
187
|
|
|
168
188
|
rl.on('line', async (input) => {
|
|
189
|
+
clearPreview();
|
|
169
190
|
const rawInput = input.trim();
|
|
170
|
-
const cmd = rawInput.toLowerCase();
|
|
171
|
-
|
|
172
191
|
if (rawInput === '') { displayPrompt(); return; }
|
|
173
192
|
|
|
174
|
-
|
|
193
|
+
const cmd = rawInput.toLowerCase();
|
|
175
194
|
if (cmd === '/exit' || cmd === 'sair') process.exit(0);
|
|
176
195
|
if (cmd === '/chat') { currentMode = 'chat'; displayPrompt(); return; }
|
|
177
196
|
if (cmd === '/plan') { currentMode = 'plan'; displayPrompt(); return; }
|
|
178
197
|
if (cmd === '/edit' || cmd === '/edit manual') { currentMode = 'edit'; editState.autoAccept = false; displayPrompt(); return; }
|
|
179
198
|
if (cmd === '/edit auto') { currentMode = 'edit'; editState.autoAccept = true; displayPrompt(); return; }
|
|
180
|
-
|
|
181
199
|
if (cmd === '/clear') { resetMessages(); console.clear(); displayPrompt(); return; }
|
|
182
|
-
if (cmd === '/help') { console.log(gray(t.help)); displayPrompt(); return; }
|
|
183
200
|
|
|
184
201
|
if (cmd === '/init') {
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
202
|
+
console.log(chalk.cyan('\n🚀 Gerando .bimmorc.json...\n'));
|
|
203
|
+
const initPrompt = `Analise o projeto e crie o .bimmorc.json com regras e stack. Use write_file.`;
|
|
204
|
+
const spinner = ora({ text: lavender(`bimmo pensando...`), color: 'red' }).start();
|
|
205
|
+
try {
|
|
206
|
+
const res = await provider.sendMessage([...messages, { role: 'user', content: initPrompt }]);
|
|
207
|
+
spinner.stop();
|
|
208
|
+
console.log(marked.parse(cleanAIResponse(res)));
|
|
209
|
+
} catch (e) { spinner.stop(); console.error(chalk.red(e.message)); }
|
|
210
|
+
resetMessages(); displayPrompt(); return;
|
|
192
211
|
}
|
|
193
212
|
|
|
194
|
-
if (cmd === '/config') {
|
|
195
|
-
rl.pause();
|
|
196
|
-
await configure();
|
|
197
|
-
config = getConfig();
|
|
198
|
-
provider = createProvider(config);
|
|
199
|
-
rl.resume();
|
|
200
|
-
displayPrompt();
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (cmd.startsWith('/switch ')) {
|
|
205
|
-
const pName = rawInput.split(' ')[1];
|
|
206
|
-
if (switchProfile(pName)) {
|
|
207
|
-
config = getConfig(); provider = createProvider(config);
|
|
208
|
-
console.log(green(`\n✓ ${t.switchOk}`));
|
|
209
|
-
} else { console.log(chalk.red(`\n✖ Perfil não encontrado.`)); }
|
|
210
|
-
displayPrompt(); return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (cmd.startsWith('/use ')) {
|
|
214
|
-
const aName = rawInput.split(' ')[1];
|
|
215
|
-
if (aName === 'normal') { activePersona = null; resetMessages(); displayPrompt(); return; }
|
|
216
|
-
const agents = config.agents || {};
|
|
217
|
-
if (agents[aName]) {
|
|
218
|
-
activePersona = aName;
|
|
219
|
-
const agent = agents[aName];
|
|
220
|
-
if (switchProfile(agent.profile)) { config = getConfig(); provider = createProvider(config); }
|
|
221
|
-
currentMode = agent.mode || 'chat';
|
|
222
|
-
console.log(green(`\n✓ ${t.agentOk} ${bold(aName)}`));
|
|
223
|
-
resetMessages();
|
|
224
|
-
} else { console.log(chalk.red(`\n✖ Agente não encontrado.`)); }
|
|
225
|
-
displayPrompt(); return;
|
|
226
|
-
}
|
|
213
|
+
if (cmd === '/config') { rl.pause(); await configure(); config = getConfig(); provider = createProvider(config); rl.resume(); displayPrompt(); return; }
|
|
227
214
|
|
|
228
215
|
// PROCESSAMENTO IA
|
|
229
216
|
const controller = new AbortController();
|
|
230
217
|
const abortHandler = () => controller.abort();
|
|
218
|
+
process.removeListener('SIGINT', () => {}); // Limpa handlers antigos
|
|
231
219
|
process.on('SIGINT', abortHandler);
|
|
232
220
|
|
|
233
221
|
let modeInstr = "";
|
|
234
222
|
if (currentMode === 'plan') modeInstr = "\n[MODO PLAN] Apenas analise.";
|
|
235
223
|
else if (currentMode === 'edit') modeInstr = `\n[MODO EDIT] Auto-Accept: ${editState.autoAccept ? 'ON' : 'OFF'}`;
|
|
236
224
|
|
|
237
|
-
// Processar anexos @
|
|
238
225
|
const processedContent = [];
|
|
239
226
|
const words = rawInput.split(' ');
|
|
240
227
|
for (const word of words) {
|
|
241
228
|
if (word.startsWith('@')) {
|
|
242
229
|
const filePath = word.slice(1);
|
|
243
230
|
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
244
|
-
|
|
245
|
-
processedContent.push({ type: 'text', text: `\n[ARQUIVO: ${filePath}]\n${content}\n` });
|
|
231
|
+
processedContent.push({ type: 'text', text: `\n[ARQUIVO: ${filePath}]\n${fs.readFileSync(filePath, 'utf-8')}\n` });
|
|
246
232
|
} else { processedContent.push({ type: 'text', text: word }); }
|
|
247
233
|
} else { processedContent.push({ type: 'text', text: word }); }
|
|
248
234
|
}
|
|
249
235
|
|
|
250
236
|
messages.push({ role: 'user', content: [...processedContent, { type: 'text', text: modeInstr }] });
|
|
251
|
-
|
|
252
|
-
const spinner = ora({ text: lavender(`${t.thinking} (Ctrl+C para parar)`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
|
|
237
|
+
const spinner = ora({ text: lavender(`bimmo pensando... (Ctrl+C para parar)`), color: currentMode === 'edit' ? 'red' : 'magenta' }).start();
|
|
253
238
|
|
|
254
239
|
try {
|
|
255
240
|
let responseText = await provider.sendMessage(messages, { signal: controller.signal });
|
|
256
241
|
spinner.stop();
|
|
257
|
-
|
|
258
|
-
const cleanedText = cleanAIResponse(responseText);
|
|
259
|
-
messages.push({ role: 'assistant', content: responseText });
|
|
260
|
-
|
|
261
242
|
console.log(`\n${lavender('bimmo ')}${currentMode.toUpperCase()}`);
|
|
262
243
|
console.log(lavender('─'.repeat(50)));
|
|
263
|
-
|
|
264
|
-
console.log(gray('─'.repeat(50)));
|
|
244
|
+
process.stdout.write(marked.parse(cleanAIResponse(responseText)));
|
|
245
|
+
console.log(gray('\n' + '─'.repeat(50)));
|
|
246
|
+
messages.push({ role: 'assistant', content: responseText });
|
|
265
247
|
} catch (err) {
|
|
266
248
|
spinner.stop();
|
|
267
|
-
if (controller.signal.aborted) { console.log(yellow(`\n⚠️
|
|
249
|
+
if (controller.signal.aborted) { console.log(yellow(`\n⚠️ Interrompido.`)); messages.pop(); }
|
|
268
250
|
else { console.error(chalk.red(`\n✖ Erro: ${err.message}`)); }
|
|
269
251
|
} finally {
|
|
270
252
|
process.removeListener('SIGINT', abortHandler);
|
|
@@ -18,6 +18,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
18
18
|
type: 'image',
|
|
19
19
|
source: { type: 'base64', media_type: part.mimeType, data: part.data }
|
|
20
20
|
};
|
|
21
|
+
return part;
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -51,8 +52,6 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
51
52
|
|
|
52
53
|
if (tool) {
|
|
53
54
|
if (options.signal?.aborted) throw new Error('Abortado pelo usuário');
|
|
54
|
-
|
|
55
|
-
console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
|
|
56
55
|
const result = await tool.execute(toolUse.input);
|
|
57
56
|
|
|
58
57
|
const nextMessages = [
|
package/src/providers/gemini.js
CHANGED
|
@@ -15,6 +15,7 @@ export class GeminiProvider extends BaseProvider {
|
|
|
15
15
|
if (part.type === 'image') return {
|
|
16
16
|
inlineData: { mimeType: part.mimeType, data: part.data }
|
|
17
17
|
};
|
|
18
|
+
return part;
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -58,7 +59,6 @@ export class GeminiProvider extends BaseProvider {
|
|
|
58
59
|
|
|
59
60
|
const tool = tools.find(t => t.name === call.functionCall.name);
|
|
60
61
|
if (tool) {
|
|
61
|
-
console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
|
|
62
62
|
const toolResult = await tool.execute(call.functionCall.args);
|
|
63
63
|
|
|
64
64
|
const resultResponse = await chat.sendMessage([{
|
package/src/providers/openai.js
CHANGED
|
@@ -21,12 +21,8 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
21
21
|
|
|
22
22
|
formatMessages(messages) {
|
|
23
23
|
return messages.map(msg => {
|
|
24
|
-
|
|
25
|
-
if (typeof msg.content === 'string' || msg.content === null) {
|
|
26
|
-
return msg;
|
|
27
|
-
}
|
|
24
|
+
if (typeof msg.content === 'string' || msg.content === null) return msg;
|
|
28
25
|
|
|
29
|
-
// Se for um array (multimodal), processa as partes
|
|
30
26
|
if (Array.isArray(msg.content)) {
|
|
31
27
|
const content = msg.content.map(part => {
|
|
32
28
|
if (part.type === 'text') return { type: 'text', text: part.text };
|
|
@@ -34,11 +30,10 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
34
30
|
type: 'image_url',
|
|
35
31
|
image_url: { url: `data:${part.mimeType};base64,${part.data}` }
|
|
36
32
|
};
|
|
37
|
-
return part;
|
|
33
|
+
return part;
|
|
38
34
|
});
|
|
39
35
|
return { ...msg, content };
|
|
40
36
|
}
|
|
41
|
-
|
|
42
37
|
return msg;
|
|
43
38
|
});
|
|
44
39
|
}
|
|
@@ -77,7 +72,6 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
77
72
|
|
|
78
73
|
const tool = tools.find(t => t.name === toolCall.function.name);
|
|
79
74
|
if (tool) {
|
|
80
|
-
console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
|
|
81
75
|
const args = JSON.parse(toolCall.function.arguments);
|
|
82
76
|
const result = await tool.execute(args);
|
|
83
77
|
|