agent-mp 0.5.36 → 0.5.37
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/dist/commands/repl.js +169 -30
- package/dist/core/engine.js +38 -0
- package/dist/utils/deepseek.d.ts +14 -0
- package/dist/utils/deepseek.js +107 -0
- package/dist/utils/gemini.d.ts +1 -0
- package/dist/utils/gemini.js +23 -3
- package/package.json +1 -1
package/dist/commands/repl.js
CHANGED
|
@@ -11,7 +11,8 @@ import { loadAuth, saveAuth, loadCliConfig, saveCliConfig, loadProjectConfig, re
|
|
|
11
11
|
import { log } from '../utils/logger.js';
|
|
12
12
|
import { AgentEngine, ExitError } from '../core/engine.js';
|
|
13
13
|
import { qwenAuthStatus, QWEN_AGENT_HOME, fetchQwenModels, loadApiKeyConfig, saveApiKeyConfig, fetchApiKeyModels, DEFAULT_API_BASE_URL } from '../utils/qwen-auth.js';
|
|
14
|
-
import { loadGeminiApiKey, saveGeminiApiKey, geminiAuthStatus, deleteGeminiApiKey, GEMINI_MODELS } from '../utils/gemini.js';
|
|
14
|
+
import { loadGeminiApiKey, saveGeminiApiKey, geminiAuthStatus, deleteGeminiApiKey, GEMINI_MODELS, fetchGeminiModels } from '../utils/gemini.js';
|
|
15
|
+
import { loadDeepSeekApiKey, saveDeepSeekApiKey, deleteDeepSeekApiKey, DEEPSEEK_MODELS, fetchDeepSeekModels } from '../utils/deepseek.js';
|
|
15
16
|
import { renderWelcomePanel, renderHelpHint, renderSectionBox, renderMultiSectionBox } from '../ui/theme.js';
|
|
16
17
|
import { FixedInput } from '../ui/input.js';
|
|
17
18
|
import { newSession, saveSession } from '../utils/sessions.js';
|
|
@@ -314,33 +315,88 @@ async function promptGeminiKeySetup(rl, askFn) {
|
|
|
314
315
|
console.log(chalk.red(' API key es requerida.'));
|
|
315
316
|
return false;
|
|
316
317
|
}
|
|
317
|
-
console.log(chalk.
|
|
318
|
-
|
|
318
|
+
console.log(chalk.dim('\n Fetching available models...'));
|
|
319
|
+
const tempCfg = { api_key: resolvedKey, model: '' };
|
|
320
|
+
const models = await fetchGeminiModels(tempCfg);
|
|
319
321
|
const defaultModel = existing?.model ?? 'gemini-2.5-flash';
|
|
320
|
-
const pick = await askFn(`\n Modelo [${defaultModel}]: `);
|
|
321
|
-
const num = parseInt(pick.trim());
|
|
322
322
|
let chosenModel = defaultModel;
|
|
323
|
-
if (
|
|
324
|
-
|
|
323
|
+
if (models.length > 0) {
|
|
324
|
+
console.log(chalk.bold('\n Modelos disponibles:'));
|
|
325
|
+
models.forEach((m, i) => console.log(chalk.dim(` ${i + 1}. ${m}`)));
|
|
326
|
+
const pick = await askFn(`\n Modelo [${chosenModel}]: `);
|
|
327
|
+
const num = parseInt(pick.trim());
|
|
328
|
+
if (!isNaN(num) && num >= 1 && num <= models.length) {
|
|
329
|
+
chosenModel = models[num - 1];
|
|
330
|
+
}
|
|
331
|
+
else if (pick.trim()) {
|
|
332
|
+
chosenModel = pick.trim();
|
|
333
|
+
}
|
|
325
334
|
}
|
|
326
|
-
else
|
|
327
|
-
|
|
335
|
+
else {
|
|
336
|
+
console.log(chalk.yellow(' No se pudieron cargar modelos de la API — usando lista estática.'));
|
|
337
|
+
GEMINI_MODELS.forEach((m, i) => console.log(chalk.dim(` ${i + 1}. ${m}`)));
|
|
338
|
+
const pick = await askFn(` Modelo [${chosenModel}]: `);
|
|
339
|
+
if (pick.trim())
|
|
340
|
+
chosenModel = pick.trim();
|
|
328
341
|
}
|
|
329
342
|
await saveGeminiApiKey({ api_key: resolvedKey, model: chosenModel });
|
|
330
343
|
console.log(chalk.green(`\n ✓ Gemini API key guardada — ${chosenModel}\n`));
|
|
331
344
|
return true;
|
|
332
345
|
}
|
|
346
|
+
async function promptDeepseekKeySetup(rl, askFn) {
|
|
347
|
+
const existing = await loadDeepSeekApiKey();
|
|
348
|
+
if (existing) {
|
|
349
|
+
console.log(chalk.dim(` Current: DeepSeek / ${existing.model}`));
|
|
350
|
+
}
|
|
351
|
+
const apiKey = await askFn(` DeepSeek API Key${existing ? ' [Enter to keep]' : ''}: `);
|
|
352
|
+
const resolvedKey = apiKey.trim() || existing?.api_key || '';
|
|
353
|
+
if (!resolvedKey) {
|
|
354
|
+
console.log(chalk.red(' API key es requerida.'));
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
console.log(chalk.dim('\n Fetching available models...'));
|
|
358
|
+
const tempCfg = { api_key: resolvedKey, model: '' };
|
|
359
|
+
const models = await fetchDeepSeekModels(tempCfg);
|
|
360
|
+
const defaultModel = existing?.model ?? 'deepseek-chat';
|
|
361
|
+
let chosenModel = defaultModel;
|
|
362
|
+
if (models.length > 0) {
|
|
363
|
+
console.log(chalk.bold('\n Modelos disponibles:'));
|
|
364
|
+
models.forEach((m, i) => console.log(chalk.dim(` ${i + 1}. ${m}`)));
|
|
365
|
+
const pick = await askFn(`\n Modelo [${chosenModel}]: `);
|
|
366
|
+
const num = parseInt(pick.trim());
|
|
367
|
+
if (!isNaN(num) && num >= 1 && num <= models.length) {
|
|
368
|
+
chosenModel = models[num - 1];
|
|
369
|
+
}
|
|
370
|
+
else if (pick.trim()) {
|
|
371
|
+
chosenModel = pick.trim();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
console.log(chalk.yellow(' No se pudieron cargar modelos de la API — usando lista estática.'));
|
|
376
|
+
DEEPSEEK_MODELS.forEach((m, i) => console.log(chalk.dim(` ${i + 1}. ${m}`)));
|
|
377
|
+
const pick = await askFn(` Modelo [${chosenModel}]: `);
|
|
378
|
+
if (pick.trim())
|
|
379
|
+
chosenModel = pick.trim();
|
|
380
|
+
}
|
|
381
|
+
await saveDeepSeekApiKey({ api_key: resolvedKey, model: chosenModel });
|
|
382
|
+
console.log(chalk.green(`\n ✓ DeepSeek API key guardada — ${chosenModel}\n`));
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
333
385
|
async function cmdLogin(rl) {
|
|
334
386
|
console.log(chalk.bold.cyan('\n Configure API Key\n'));
|
|
335
387
|
console.log(chalk.bold(' Proveedor:'));
|
|
336
388
|
console.log(chalk.dim(' 1. Qwen / OpenAI-compatible'));
|
|
337
389
|
console.log(chalk.dim(' 2. Google Gemini'));
|
|
390
|
+
console.log(chalk.dim(' 3. DeepSeek'));
|
|
338
391
|
console.log('');
|
|
339
392
|
const pick = await ask(rl, ' Elegí [1]: ');
|
|
340
393
|
const choice = pick.trim() || '1';
|
|
341
394
|
if (choice === '2') {
|
|
342
395
|
return promptGeminiKeySetup(rl, (q) => ask(rl, q));
|
|
343
396
|
}
|
|
397
|
+
if (choice === '3') {
|
|
398
|
+
return promptDeepseekKeySetup(rl, (q) => ask(rl, q));
|
|
399
|
+
}
|
|
344
400
|
return promptApiKeySetup(rl, (q) => ask(rl, q));
|
|
345
401
|
}
|
|
346
402
|
async function cmdSetup(rl) {
|
|
@@ -676,17 +732,30 @@ async function cmdStatus(fi) {
|
|
|
676
732
|
}
|
|
677
733
|
async function cmdModels(roleArg, fi, rl) {
|
|
678
734
|
const geminiCfg = await loadGeminiApiKey();
|
|
735
|
+
const deepseekCfg = await loadDeepSeekApiKey();
|
|
679
736
|
const apiKeyCfg = await loadApiKeyConfig();
|
|
680
737
|
const auth = await loadAuth();
|
|
681
738
|
// Determine active provider from current gCoordinatorCmd
|
|
682
739
|
const isGeminiActive = gCoordinatorCmd.startsWith('gemini-direct');
|
|
683
|
-
const
|
|
740
|
+
const isDeepSeekActive = gCoordinatorCmd.startsWith('deepseek-direct');
|
|
741
|
+
const activeProvider = isGeminiActive ? 'gemini'
|
|
742
|
+
: isDeepSeekActive ? 'deepseek'
|
|
743
|
+
: (apiKeyCfg?.provider ?? auth.entries[0]?.provider ?? '');
|
|
684
744
|
const resume = fi.suspend();
|
|
685
745
|
const cliConfig = await loadCliConfig();
|
|
686
746
|
const currentModel = cliConfig.coordinatorModel ?? '(auto)';
|
|
687
747
|
let models;
|
|
688
748
|
if (isGeminiActive || activeProvider === 'gemini') {
|
|
689
|
-
|
|
749
|
+
console.log(chalk.dim(' Fetching Gemini models...'));
|
|
750
|
+
models = await fetchGeminiModels(geminiCfg);
|
|
751
|
+
if (!models.length)
|
|
752
|
+
models = GEMINI_MODELS;
|
|
753
|
+
}
|
|
754
|
+
else if (isDeepSeekActive || activeProvider === 'deepseek') {
|
|
755
|
+
console.log(chalk.dim(' Fetching DeepSeek models...'));
|
|
756
|
+
models = await fetchDeepSeekModels(deepseekCfg);
|
|
757
|
+
if (!models.length)
|
|
758
|
+
models = DEEPSEEK_MODELS;
|
|
690
759
|
}
|
|
691
760
|
else if (activeProvider === 'qwen') {
|
|
692
761
|
console.log(chalk.dim(' Fetching Qwen models...'));
|
|
@@ -726,6 +795,11 @@ async function cmdModels(roleArg, fi, rl) {
|
|
|
726
795
|
if (geminiCfg)
|
|
727
796
|
await saveGeminiApiKey({ ...geminiCfg, model: selectedModel });
|
|
728
797
|
}
|
|
798
|
+
else if (isDeepSeekActive || activeProvider === 'deepseek') {
|
|
799
|
+
gCoordinatorCmd = `deepseek-direct -m ${selectedModel}`;
|
|
800
|
+
if (deepseekCfg)
|
|
801
|
+
await saveDeepSeekApiKey({ ...deepseekCfg, model: selectedModel });
|
|
802
|
+
}
|
|
729
803
|
else {
|
|
730
804
|
gCoordinatorCmd = buildCmd(activeProvider, selectedModel);
|
|
731
805
|
}
|
|
@@ -736,24 +810,29 @@ async function cmdModels(roleArg, fi, rl) {
|
|
|
736
810
|
/** /provider — switch coordinator provider between configured ones */
|
|
737
811
|
async function cmdProvider(fi, rl) {
|
|
738
812
|
const geminiCfg = await loadGeminiApiKey();
|
|
813
|
+
const deepseekCfg = await loadDeepSeekApiKey();
|
|
739
814
|
const apiKeyCfg = await loadApiKeyConfig();
|
|
740
815
|
const options = [];
|
|
741
816
|
if (geminiCfg?.api_key)
|
|
742
817
|
options.push({ label: `Google Gemini (${geminiCfg.model})`, value: 'gemini' });
|
|
818
|
+
if (deepseekCfg?.api_key)
|
|
819
|
+
options.push({ label: `DeepSeek (${deepseekCfg.model})`, value: 'deepseek' });
|
|
743
820
|
if (apiKeyCfg?.api_key)
|
|
744
821
|
options.push({ label: `${apiKeyCfg.provider} (${apiKeyCfg.model})`, value: apiKeyCfg.provider });
|
|
745
822
|
if (options.length === 0) {
|
|
746
823
|
fi.println(chalk.yellow(' No hay proveedores configurados.'));
|
|
747
|
-
fi.println(chalk.dim(' Usá /login para configurar Qwen o
|
|
824
|
+
fi.println(chalk.dim(' Usá /login para configurar Qwen, Gemini o DeepSeek.'));
|
|
748
825
|
return;
|
|
749
826
|
}
|
|
750
827
|
if (options.length === 1) {
|
|
751
828
|
fi.println(chalk.dim(` Activo: ${options[0].label}`));
|
|
752
|
-
fi.println(chalk.dim(' Para agregar
|
|
829
|
+
fi.println(chalk.dim(' Para agregar otro: /login'));
|
|
753
830
|
return;
|
|
754
831
|
}
|
|
755
832
|
const resume = fi.suspend();
|
|
756
|
-
const
|
|
833
|
+
const isGeminiActive = gCoordinatorCmd.startsWith('gemini-direct');
|
|
834
|
+
const isDeepSeekActive = gCoordinatorCmd.startsWith('deepseek-direct');
|
|
835
|
+
const current = isGeminiActive ? 'gemini' : isDeepSeekActive ? 'deepseek' : (apiKeyCfg?.provider ?? '?');
|
|
757
836
|
console.log(chalk.bold.cyan(`\n Cambiar proveedor [activo: ${current}]\n`));
|
|
758
837
|
options.forEach((o, i) => console.log(chalk.dim(` ${i + 1}. ${o.label}`)));
|
|
759
838
|
const pick = await ask(rl, chalk.bold('\n Proveedor # (Enter to keep): '));
|
|
@@ -771,10 +850,17 @@ async function cmdProvider(fi, rl) {
|
|
|
771
850
|
const cliConfig = await loadCliConfig();
|
|
772
851
|
if (chosen.value === 'gemini') {
|
|
773
852
|
gCoordinatorCmd = `gemini-direct -m ${geminiCfg.model}`;
|
|
853
|
+
cliConfig.coordinatorProvider = 'gemini';
|
|
854
|
+
}
|
|
855
|
+
else if (chosen.value === 'deepseek') {
|
|
856
|
+
gCoordinatorCmd = `deepseek-direct -m ${deepseekCfg.model}`;
|
|
857
|
+
cliConfig.coordinatorProvider = 'deepseek';
|
|
774
858
|
}
|
|
775
859
|
else {
|
|
776
860
|
gCoordinatorCmd = `qwen-direct -m ${apiKeyCfg.model}`;
|
|
861
|
+
cliConfig.coordinatorProvider = apiKeyCfg.provider;
|
|
777
862
|
}
|
|
863
|
+
await saveCliConfig(cliConfig);
|
|
778
864
|
fi.println(chalk.green(`\n ✓ Proveedor → ${chosen.label}`));
|
|
779
865
|
fi.println(chalk.dim(` CMD: ${gCoordinatorCmd}`));
|
|
780
866
|
fi.println('');
|
|
@@ -859,8 +945,8 @@ function cmdHelp(fi) {
|
|
|
859
945
|
{ key: '/impl <task-id>', value: 'Lanza SOLO implementor — ejecuta plan existente' },
|
|
860
946
|
{ key: '/rev <task-id>', value: 'Lanza SOLO reviewer — valida implementación' },
|
|
861
947
|
{ key: '/model', value: 'Cambiar modelo del coordinador' },
|
|
862
|
-
{ key: '/provider', value: 'Cambiar proveedor activo (Gemini / Qwen)' },
|
|
863
|
-
{ key: '/login', value: 'Configurar API key (Qwen o
|
|
948
|
+
{ key: '/provider', value: 'Cambiar proveedor activo (Gemini / DeepSeek / Qwen)' },
|
|
949
|
+
{ key: '/login', value: 'Configurar API key (Qwen, Gemini o DeepSeek)' },
|
|
864
950
|
{ key: '/logout', value: 'Logout and clear credentials' },
|
|
865
951
|
{ key: '/auth-status', value: 'Show authentication status' },
|
|
866
952
|
{ key: '/usage', value: 'Check quota status for all role CLIs' },
|
|
@@ -911,21 +997,59 @@ export async function initCoordinator() {
|
|
|
911
997
|
console.log(chalk.red(' No CLIs detected. Install at least one: qwen, claude, gemini, codex'));
|
|
912
998
|
return null;
|
|
913
999
|
}
|
|
914
|
-
// Step 2: Pick coordinator
|
|
1000
|
+
// Step 2: Pick coordinator — respect saved preference first
|
|
915
1001
|
const auth = await loadAuth();
|
|
916
1002
|
const currentAuth = auth.activeProvider;
|
|
917
1003
|
let activeCli = installed.find((c) => c.name === currentAuth);
|
|
918
|
-
//
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1004
|
+
// Check saved coordinatorProvider preference
|
|
1005
|
+
const cliConfig = await loadCliConfig();
|
|
1006
|
+
const savedProvider = cliConfig.coordinatorProvider;
|
|
1007
|
+
// Helper: try to activate a specific provider
|
|
1008
|
+
const tryProvider = async (provider) => {
|
|
1009
|
+
if (provider === 'gemini') {
|
|
1010
|
+
const cfg = await loadGeminiApiKey();
|
|
1011
|
+
if (cfg?.api_key) {
|
|
1012
|
+
return {
|
|
1013
|
+
syntheticCli: { name: 'gemini', info: { command: 'gemini-direct', modelFlag: '-m', promptFlag: '-p', description: 'Gemini API key' }, path: 'gemini-direct' },
|
|
1014
|
+
cmd: `gemini-direct -m ${cfg.model}`,
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (provider === 'deepseek') {
|
|
1019
|
+
const cfg = await loadDeepSeekApiKey();
|
|
1020
|
+
if (cfg?.api_key) {
|
|
1021
|
+
return {
|
|
1022
|
+
syntheticCli: { name: 'deepseek', info: { command: 'deepseek-direct', modelFlag: '-m', promptFlag: '-p', description: 'DeepSeek API key' }, path: 'deepseek-direct' },
|
|
1023
|
+
cmd: `deepseek-direct -m ${cfg.model}`,
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
const apiKeyCfg = await loadApiKeyConfig();
|
|
1028
|
+
if (apiKeyCfg?.api_key && apiKeyCfg.provider === provider) {
|
|
1029
|
+
return {
|
|
1030
|
+
syntheticCli: { name: apiKeyCfg.provider, info: { command: 'qwen-direct', modelFlag: '-m', promptFlag: '-p', description: 'API key' }, path: 'qwen-direct' },
|
|
1031
|
+
cmd: `qwen-direct -m ${apiKeyCfg.model}`,
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
return null;
|
|
1035
|
+
};
|
|
1036
|
+
// Try saved provider first
|
|
1037
|
+
if (savedProvider) {
|
|
1038
|
+
const saved = await tryProvider(savedProvider);
|
|
1039
|
+
if (saved) {
|
|
1040
|
+
gCoordinatorCmd = saved.cmd;
|
|
1041
|
+
console.log(chalk.green(` ✓ Auth: ${saved.syntheticCli.name} / ${saved.cmd.match(/-m\s+(\S+)/)?.[1] ?? '?'}\n`));
|
|
1042
|
+
return { activeCli: saved.syntheticCli, installed, coordinatorCmd: gCoordinatorCmd };
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
// Fallback: try providers in default order
|
|
1046
|
+
for (const p of ['gemini', 'deepseek']) {
|
|
1047
|
+
const r = await tryProvider(p);
|
|
1048
|
+
if (r) {
|
|
1049
|
+
gCoordinatorCmd = r.cmd;
|
|
1050
|
+
console.log(chalk.green(` ✓ Auth: ${r.syntheticCli.name} / ${r.cmd.match(/-m\s+(\S+)/)?.[1] ?? '?'}\n`));
|
|
1051
|
+
return { activeCli: r.syntheticCli, installed, coordinatorCmd: gCoordinatorCmd };
|
|
1052
|
+
}
|
|
929
1053
|
}
|
|
930
1054
|
// Fast-path: Qwen/OpenAI-compatible API key configured
|
|
931
1055
|
const apiKeyCfg = await loadApiKeyConfig();
|
|
@@ -947,8 +1071,7 @@ export async function initCoordinator() {
|
|
|
947
1071
|
});
|
|
948
1072
|
if (doSetup) {
|
|
949
1073
|
const askFn = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
950
|
-
|
|
951
|
-
rl.question(' Provider: 1=Qwen/OpenAI-compatible 2=Google Gemini [1]: ', async () => { });
|
|
1074
|
+
rl.question(' Provider: 1=Qwen/OpenAI-compatible 2=Google Gemini 3=DeepSeek [1]: ', async () => { });
|
|
952
1075
|
const providerPick = await new Promise((resolve) => rl.question(' Provider [1]: ', resolve));
|
|
953
1076
|
if (providerPick.trim() === '2') {
|
|
954
1077
|
const ok = await promptGeminiKeySetup(rl, askFn);
|
|
@@ -965,6 +1088,21 @@ export async function initCoordinator() {
|
|
|
965
1088
|
};
|
|
966
1089
|
return { coordinatorCmd: gCoordinatorCmd, activeCli: syntheticCli, installed };
|
|
967
1090
|
}
|
|
1091
|
+
if (providerPick.trim() === '3') {
|
|
1092
|
+
const ok = await promptDeepseekKeySetup(rl, askFn);
|
|
1093
|
+
if (!ok)
|
|
1094
|
+
return null;
|
|
1095
|
+
const saved = await loadDeepSeekApiKey();
|
|
1096
|
+
if (!saved)
|
|
1097
|
+
return null;
|
|
1098
|
+
gCoordinatorCmd = `deepseek-direct -m ${saved.model}`;
|
|
1099
|
+
const syntheticCli = {
|
|
1100
|
+
name: 'deepseek',
|
|
1101
|
+
info: { command: 'deepseek-direct', modelFlag: '-m', promptFlag: '-p', description: 'DeepSeek API key' },
|
|
1102
|
+
path: 'deepseek-direct',
|
|
1103
|
+
};
|
|
1104
|
+
return { coordinatorCmd: gCoordinatorCmd, activeCli: syntheticCli, installed };
|
|
1105
|
+
}
|
|
968
1106
|
const ok = await promptApiKeySetup(rl, askFn);
|
|
969
1107
|
if (!ok)
|
|
970
1108
|
return null;
|
|
@@ -1267,6 +1405,7 @@ export async function runRepl(resumeSession) {
|
|
|
1267
1405
|
await fs.unlink(path.join(QWEN_AGENT_HOME, 'oauth_creds.json')).catch(() => { });
|
|
1268
1406
|
await fs.unlink(await getApiKeyConfigPath()).catch(() => { });
|
|
1269
1407
|
await deleteGeminiApiKey();
|
|
1408
|
+
await deleteDeepSeekApiKey();
|
|
1270
1409
|
const authStore = await loadAuth();
|
|
1271
1410
|
authStore.entries = [];
|
|
1272
1411
|
delete authStore.activeProvider;
|
package/dist/core/engine.js
CHANGED
|
@@ -8,6 +8,7 @@ import { log } from '../utils/logger.js';
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { callQwenAPI, callQwenAPIFromCreds } from '../utils/qwen-auth.js';
|
|
10
10
|
import { callGeminiAPI } from '../utils/gemini.js';
|
|
11
|
+
import { callDeepSeekAPI } from '../utils/deepseek.js';
|
|
11
12
|
import * as fs from 'fs/promises';
|
|
12
13
|
/** Thrown when a slash command inside a conversation requests exit */
|
|
13
14
|
export class ExitError extends Error {
|
|
@@ -97,6 +98,13 @@ function runCli(cmd, prompt, timeoutMs = 600000, envOverride, onData) {
|
|
|
97
98
|
.then(output => ({ output, exitCode: 0 }))
|
|
98
99
|
.catch(err => ({ output: `Error: ${err.message}`, exitCode: 1 }));
|
|
99
100
|
}
|
|
101
|
+
// Si el comando es "deepseek-direct", usar la API directa sin CLI
|
|
102
|
+
if (cmd.startsWith('deepseek-direct')) {
|
|
103
|
+
const model = cmd.includes('-m') ? cmd.split('-m')[1]?.trim().split(/\s/)[0] : 'deepseek-chat';
|
|
104
|
+
return callDeepSeekAPI(prompt, model)
|
|
105
|
+
.then(output => ({ output, exitCode: 0 }))
|
|
106
|
+
.catch(err => ({ output: `Error: ${err.message}`, exitCode: 1 }));
|
|
107
|
+
}
|
|
100
108
|
return new Promise((resolve, reject) => {
|
|
101
109
|
const parts = cmd.trim().split(/\s+/);
|
|
102
110
|
let fullCmd;
|
|
@@ -520,6 +528,36 @@ INSTRUCCIONES GENERALES:
|
|
|
520
528
|
throw new Error('COORDINATOR_FAILED');
|
|
521
529
|
}
|
|
522
530
|
}
|
|
531
|
+
else if (this.getCoordinatorCmd().startsWith('deepseek-direct')) {
|
|
532
|
+
const model = this.getCoordinatorCmd().match(/(?:-m|--model)\s+(\S+)/)?.[1] || 'deepseek-chat';
|
|
533
|
+
const sp = this._startSpinner(`coordinador ${model}`);
|
|
534
|
+
try {
|
|
535
|
+
const result = await callDeepSeekAPI(prompt, model, (c) => sp.push(c));
|
|
536
|
+
sp.stop();
|
|
537
|
+
return result;
|
|
538
|
+
}
|
|
539
|
+
catch (err) {
|
|
540
|
+
sp.stop();
|
|
541
|
+
const msg = err?.message ?? '';
|
|
542
|
+
if (msg.startsWith('DEEPSEEK_NOT_CONFIGURED')) {
|
|
543
|
+
console.log(chalk.red('\n ✗ DeepSeek no configurado.'));
|
|
544
|
+
console.log(chalk.yellow(' Ejecutá: /login y elegí DeepSeek.\n'));
|
|
545
|
+
}
|
|
546
|
+
else if (msg.startsWith('DEEPSEEK_NO_BALANCE')) {
|
|
547
|
+
const detail = msg.replace('DEEPSEEK_NO_BALANCE: ', '');
|
|
548
|
+
console.log(chalk.red('\n ✗ Saldo insuficiente:'));
|
|
549
|
+
console.log(chalk.yellow(` ${detail}\n`));
|
|
550
|
+
}
|
|
551
|
+
else if (msg.startsWith('DEEPSEEK_QUOTA_EXCEEDED')) {
|
|
552
|
+
console.log(chalk.red('\n ✗ ' + msg.replace('DEEPSEEK_QUOTA_EXCEEDED: ', '')));
|
|
553
|
+
console.log(chalk.yellow(' Usá /model para cambiar de modelo o /provider para cambiar de proveedor.\n'));
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
console.log(chalk.red(`\n ✗ Error DeepSeek: ${msg}`));
|
|
557
|
+
}
|
|
558
|
+
throw new Error('COORDINATOR_FAILED');
|
|
559
|
+
}
|
|
560
|
+
}
|
|
523
561
|
else if (this.getCoordinatorCmd().startsWith('qwen')) {
|
|
524
562
|
// Use Qwen API directly — avoids the qwen CLI's own OAuth flow
|
|
525
563
|
// which causes mid-session auth popups and breaks display.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface DeepSeekKeyConfig {
|
|
2
|
+
api_key: string;
|
|
3
|
+
model: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function loadDeepSeekApiKey(): Promise<DeepSeekKeyConfig | null>;
|
|
6
|
+
export declare function saveDeepSeekApiKey(cfg: DeepSeekKeyConfig): Promise<void>;
|
|
7
|
+
export declare function deleteDeepSeekApiKey(): Promise<void>;
|
|
8
|
+
export declare function deepseekAuthStatus(): Promise<{
|
|
9
|
+
authenticated: boolean;
|
|
10
|
+
model?: string;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function callDeepSeekAPI(prompt: string, model?: string, onData?: (chunk: string) => void): Promise<string>;
|
|
13
|
+
export declare function fetchDeepSeekModels(cfg?: DeepSeekKeyConfig | null): Promise<string[]>;
|
|
14
|
+
export declare const DEEPSEEK_MODELS: string[];
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import OpenAI from 'openai';
|
|
4
|
+
import { AGENT_HOME } from './config.js';
|
|
5
|
+
const DEEPSEEK_KEY_FILE = path.join(AGENT_HOME, 'deepseek_key.json');
|
|
6
|
+
const DEEPSEEK_BASE_URL = 'https://api.deepseek.com/v1';
|
|
7
|
+
export async function loadDeepSeekApiKey() {
|
|
8
|
+
try {
|
|
9
|
+
const content = await fs.readFile(DEEPSEEK_KEY_FILE, 'utf-8');
|
|
10
|
+
const cfg = JSON.parse(content);
|
|
11
|
+
if (!cfg.api_key)
|
|
12
|
+
return null;
|
|
13
|
+
return cfg;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function saveDeepSeekApiKey(cfg) {
|
|
20
|
+
await fs.mkdir(AGENT_HOME, { recursive: true });
|
|
21
|
+
await fs.writeFile(DEEPSEEK_KEY_FILE, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
22
|
+
}
|
|
23
|
+
export async function deleteDeepSeekApiKey() {
|
|
24
|
+
await fs.unlink(DEEPSEEK_KEY_FILE).catch(() => { });
|
|
25
|
+
}
|
|
26
|
+
export async function deepseekAuthStatus() {
|
|
27
|
+
const cfg = await loadDeepSeekApiKey();
|
|
28
|
+
if (!cfg?.api_key)
|
|
29
|
+
return { authenticated: false };
|
|
30
|
+
return { authenticated: true, model: cfg.model };
|
|
31
|
+
}
|
|
32
|
+
export async function callDeepSeekAPI(prompt, model = 'deepseek-chat', onData) {
|
|
33
|
+
const cfg = await loadDeepSeekApiKey();
|
|
34
|
+
if (!cfg?.api_key) {
|
|
35
|
+
throw new Error('DEEPSEEK_NOT_CONFIGURED: Run /login y elegí DeepSeek');
|
|
36
|
+
}
|
|
37
|
+
const useModel = (model && model !== 'deepseek-model') ? model : cfg.model;
|
|
38
|
+
const client = new OpenAI({
|
|
39
|
+
apiKey: cfg.api_key,
|
|
40
|
+
baseURL: DEEPSEEK_BASE_URL,
|
|
41
|
+
});
|
|
42
|
+
const MAX_RETRIES = 3;
|
|
43
|
+
let lastErr;
|
|
44
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
45
|
+
try {
|
|
46
|
+
if (!onData) {
|
|
47
|
+
const completion = await client.chat.completions.create({
|
|
48
|
+
model: useModel,
|
|
49
|
+
messages: [{ role: 'user', content: prompt }],
|
|
50
|
+
});
|
|
51
|
+
return completion.choices[0]?.message?.content ?? '';
|
|
52
|
+
}
|
|
53
|
+
let fullText = '';
|
|
54
|
+
const stream = await client.chat.completions.create({
|
|
55
|
+
model: useModel,
|
|
56
|
+
messages: [{ role: 'user', content: prompt }],
|
|
57
|
+
stream: true,
|
|
58
|
+
});
|
|
59
|
+
for await (const chunk of stream) {
|
|
60
|
+
const delta = chunk.choices[0]?.delta?.content ?? '';
|
|
61
|
+
if (delta) {
|
|
62
|
+
fullText += delta;
|
|
63
|
+
onData(delta);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return fullText;
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
lastErr = err;
|
|
70
|
+
const msg = err?.message ?? '';
|
|
71
|
+
const status = err?.status ?? err?.response?.status;
|
|
72
|
+
if (status === 429 || msg.includes('429') || msg.includes('rate_limit')) {
|
|
73
|
+
const delaySecs = 15;
|
|
74
|
+
if (attempt < MAX_RETRIES) {
|
|
75
|
+
onData?.(`\n[DeepSeek 429 — reintentando en ${delaySecs}s...]`);
|
|
76
|
+
await new Promise(r => setTimeout(r, delaySecs * 1000));
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
throw new Error(`DEEPSEEK_QUOTA_EXCEEDED: Cuota agotada en ${useModel}. Usá /model para cambiar de modelo.`);
|
|
80
|
+
}
|
|
81
|
+
// 402 = insufficient balance
|
|
82
|
+
if (status === 402 || msg.includes('402') || msg.includes('Insufficient Balance')) {
|
|
83
|
+
throw new Error(`DEEPSEEK_NO_BALANCE: Saldo insuficiente en DeepSeek.\n` +
|
|
84
|
+
` Cargá crédito en https://platform.deepseek.com`);
|
|
85
|
+
}
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
throw lastErr;
|
|
90
|
+
}
|
|
91
|
+
export async function fetchDeepSeekModels(cfg) {
|
|
92
|
+
const resolved = cfg ?? await loadDeepSeekApiKey();
|
|
93
|
+
if (!resolved?.api_key)
|
|
94
|
+
return [];
|
|
95
|
+
try {
|
|
96
|
+
const client = new OpenAI({ apiKey: resolved.api_key, baseURL: DEEPSEEK_BASE_URL });
|
|
97
|
+
const list = await client.models.list();
|
|
98
|
+
return list.data.map((m) => m.id).sort();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export const DEEPSEEK_MODELS = [
|
|
105
|
+
'deepseek-chat',
|
|
106
|
+
'deepseek-reasoner',
|
|
107
|
+
];
|
package/dist/utils/gemini.d.ts
CHANGED
|
@@ -14,4 +14,5 @@ export declare function geminiAuthStatus(): Promise<{
|
|
|
14
14
|
* When onData is provided, streams response chunks as they arrive.
|
|
15
15
|
*/
|
|
16
16
|
export declare function callGeminiAPI(prompt: string, model?: string, onData?: (chunk: string) => void): Promise<string>;
|
|
17
|
+
export declare function fetchGeminiModels(cfg?: GeminiKeyConfig | null): Promise<string[]>;
|
|
17
18
|
export declare const GEMINI_MODELS: string[];
|
package/dist/utils/gemini.js
CHANGED
|
@@ -85,11 +85,31 @@ export async function callGeminiAPI(prompt, model = 'gemini-2.5-flash', onData)
|
|
|
85
85
|
}
|
|
86
86
|
throw lastErr;
|
|
87
87
|
}
|
|
88
|
+
export async function fetchGeminiModels(cfg) {
|
|
89
|
+
const resolved = cfg ?? await loadGeminiApiKey();
|
|
90
|
+
if (!resolved?.api_key)
|
|
91
|
+
return [];
|
|
92
|
+
try {
|
|
93
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${resolved.api_key}`, { signal: AbortSignal.timeout(10000) });
|
|
94
|
+
if (!res.ok)
|
|
95
|
+
return [];
|
|
96
|
+
const data = await res.json();
|
|
97
|
+
return (data.models ?? [])
|
|
98
|
+
.map((m) => m.name?.replace(/^models\//, ''))
|
|
99
|
+
.filter((id) => id && id.startsWith('gemini'))
|
|
100
|
+
.sort();
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
88
106
|
export const GEMINI_MODELS = [
|
|
89
|
-
'gemini-
|
|
107
|
+
'gemini-3.1-pro-preview',
|
|
108
|
+
'gemini-3-flash-preview',
|
|
109
|
+
'gemini-3.1-flash-lite-preview',
|
|
110
|
+
'gemini-2.5-pro',
|
|
90
111
|
'gemini-2.5-flash',
|
|
91
112
|
'gemini-2.0-flash',
|
|
92
|
-
'gemini-1.5-flash',
|
|
93
|
-
'gemini-2.5-pro',
|
|
94
113
|
'gemini-1.5-pro',
|
|
114
|
+
'gemini-1.5-flash',
|
|
95
115
|
];
|