agent-mp 0.5.36 → 0.5.38

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.
@@ -1,5 +1,7 @@
1
+ import * as readline from 'readline';
1
2
  import { CliInfo } from '../types.js';
2
3
  import { Session } from '../utils/sessions.js';
4
+ export declare function cmdLogin(rl: readline.Interface): Promise<boolean>;
3
5
  /**
4
6
  * Shared coordinator setup: detect CLIs, pick coordinator, check auth.
5
7
  * Returns { coordinatorCmd, activeCli, installed, rl } or null if setup failed.
@@ -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.bold('\n Modelos disponibles:'));
318
- GEMINI_MODELS.forEach((m, i) => console.log(chalk.dim(` ${i + 1}. ${m}`)));
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 (!isNaN(num) && num >= 1 && num <= GEMINI_MODELS.length) {
324
- chosenModel = GEMINI_MODELS[num - 1];
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 if (pick.trim()) {
327
- chosenModel = pick.trim();
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
  }
333
- async function cmdLogin(rl) {
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
+ }
385
+ export 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 activeProvider = isGeminiActive ? 'gemini' : (apiKeyCfg?.provider ?? auth.entries[0]?.provider ?? '');
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
- models = GEMINI_MODELS;
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 Google Gemini.'));
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 Google Gemini: /login → elegí opción 2.'));
829
+ fi.println(chalk.dim(' Para agregar otro: /login'));
753
830
  return;
754
831
  }
755
832
  const resume = fi.suspend();
756
- const current = gCoordinatorCmd.startsWith('gemini-direct') ? 'gemini' : (apiKeyCfg?.provider ?? '?');
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 Gemini)' },
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
- // Fast-path: Gemini API key configured
919
- const geminiCfg = await loadGeminiApiKey();
920
- if (geminiCfg?.api_key) {
921
- gCoordinatorCmd = `gemini-direct -m ${geminiCfg.model}`;
922
- console.log(chalk.green(` ✓ Auth: Google Gemini / ${geminiCfg.model}\n`));
923
- const syntheticCli = {
924
- name: 'gemini',
925
- info: { command: 'gemini-direct', modelFlag: '-m', promptFlag: '-p', description: 'Gemini API key' },
926
- path: 'gemini-direct',
927
- };
928
- return { activeCli: syntheticCli, installed, coordinatorCmd: gCoordinatorCmd };
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
- // Ask provider
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;
@@ -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.
package/dist/index.js CHANGED
@@ -100,32 +100,14 @@ if (nativeRole) {
100
100
  console.log(chalk.dim(` Version: ${PKG_NAME} --version\n`));
101
101
  process.exit(0);
102
102
  }
103
- // --login: configure API key for this role
103
+ // --login: multi-provider login (Qwen / Gemini / DeepSeek), same as agent-mp /login
104
104
  if (args.includes('--login') || args.includes('login')) {
105
- const { saveApiKeyConfig, loadApiKeyConfig, DEFAULT_API_BASE_URL } = await import('./utils/qwen-auth.js');
105
+ const { cmdLogin } = await import('./commands/repl.js');
106
106
  const rl = (await import('readline')).createInterface({ input: process.stdin, output: process.stdout });
107
- const ask = (q) => new Promise((res) => rl.question(q, res));
108
- const existing = await loadApiKeyConfig();
109
- console.log(chalk.bold.cyan(`\n ${PKG_NAME} — API Key Setup\n`));
110
- if (existing) {
111
- console.log(chalk.dim(` Current: ${existing.provider} / ${existing.model}`));
112
- }
113
- const apiKey = await ask(` API Key${existing ? ' [Enter to keep]' : ''}: `);
114
- const model = await ask(` Model [${existing?.model ?? 'qwen-plus'}]: `);
107
+ console.log(chalk.bold.cyan(`\n ${PKG_NAME} Login\n`));
108
+ const ok = await cmdLogin(rl);
115
109
  rl.close();
116
- const cfg = {
117
- provider: existing?.provider ?? 'openai-compatible',
118
- api_key: apiKey.trim() || existing?.api_key || '',
119
- base_url: DEFAULT_API_BASE_URL,
120
- model: model.trim() || existing?.model || 'qwen-plus',
121
- };
122
- if (!cfg.api_key) {
123
- console.log(chalk.red(' API key is required.'));
124
- process.exit(1);
125
- }
126
- await saveApiKeyConfig(cfg);
127
- console.log(chalk.green(`\n ✓ API key saved — ${cfg.provider} / ${cfg.model}\n`));
128
- process.exit(0);
110
+ process.exit(ok ? 0 : 1);
129
111
  }
130
112
  // --status: show auth status
131
113
  if (args.includes('--status') || args.includes('status')) {
@@ -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
+ ];
@@ -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[];
@@ -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-flash-latest',
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
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-mp",
3
- "version": "0.5.36",
3
+ "version": "0.5.38",
4
4
  "description": "Deterministic multi-agent CLI orchestrator — plan, code, review",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",