agent-mp 0.5.40 → 0.5.42

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.
@@ -12,7 +12,7 @@ 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
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
+ import { loadDeepSeekApiKey, saveDeepSeekApiKey, deleteDeepSeekApiKey, fetchDeepSeekModels, DEEPSEEK_DEFAULT_MODELS } from '../utils/deepseek.js';
16
16
  import { renderWelcomePanel, renderHelpHint, renderSectionBox, renderMultiSectionBox } from '../ui/theme.js';
17
17
  import { FixedInput } from '../ui/input.js';
18
18
  import { newSession, saveSession } from '../utils/sessions.js';
@@ -416,11 +416,16 @@ async function promptDeepseekKeySetup(rl, askFn) {
416
416
  }
417
417
  }
418
418
  else {
419
- console.log(chalk.yellow(' No se pudieron cargar modelos de la API — usando lista estática.'));
420
- DEEPSEEK_MODELS.forEach((m, i) => console.log(chalk.dim(` ${i + 1}. ${m}`)));
419
+ console.log(chalk.yellow(' No se pudieron cargar modelos de la API — usando defaults.'));
420
+ DEEPSEEK_DEFAULT_MODELS.forEach((m, i) => console.log(chalk.dim(` ${i + 1}. ${m}`)));
421
421
  const pick = await askFn(` Modelo [${chosenModel}]: `);
422
- if (pick.trim())
422
+ const num = parseInt(pick.trim());
423
+ if (!isNaN(num) && num >= 1 && num <= DEEPSEEK_DEFAULT_MODELS.length) {
424
+ chosenModel = DEEPSEEK_DEFAULT_MODELS[num - 1];
425
+ }
426
+ else if (pick.trim()) {
423
427
  chosenModel = pick.trim();
428
+ }
424
429
  }
425
430
  await saveDeepSeekApiKey({ api_key: resolvedKey, model: chosenModel });
426
431
  console.log(chalk.green(`\n ✓ DeepSeek API key guardada — ${chosenModel}\n`));
@@ -805,7 +810,7 @@ async function cmdModels(roleArg, fi, rl) {
805
810
  console.log(chalk.dim(' Fetching DeepSeek models...'));
806
811
  models = await fetchDeepSeekModels(deepseekCfg);
807
812
  if (!models.length)
808
- models = DEEPSEEK_MODELS;
813
+ models = DEEPSEEK_DEFAULT_MODELS;
809
814
  }
810
815
  else if (activeProvider === 'qwen') {
811
816
  console.log(chalk.dim(' Fetching Qwen models...'));
@@ -237,8 +237,9 @@ async function walkForKeyFiles(root) {
237
237
  entry: null,
238
238
  controllers: [],
239
239
  schemas: [],
240
+ allSourceFiles: [],
240
241
  };
241
- const SOURCE_EXT = /\.(ts|tsx|js|jsx|py|go|java|kt|rs|cs)$/;
242
+ const SOURCE_EXT = /\.(ts|tsx|js|jsx|py|go|java|kt|rs|cs|rb|ex|exs|swift|php|vue|svelte)$/;
242
243
  const SKIP_FILE = /\.(test|spec|d)\.[a-z]+$/i;
243
244
  const ENTRY_NAMES = new Set([
244
245
  'main.ts', 'main.js', 'main.py', 'main.go', 'Main.java',
@@ -255,8 +256,6 @@ async function walkForKeyFiles(root) {
255
256
  async function walk(dir, depth) {
256
257
  if (depth > 6)
257
258
  return;
258
- if (result.controllers.length >= 5 && result.schemas.length >= 5 && result.entry)
259
- return;
260
259
  let entries;
261
260
  try {
262
261
  entries = await fs.readdir(dir, { withFileTypes: true });
@@ -280,6 +279,8 @@ async function walkForKeyFiles(root) {
280
279
  continue;
281
280
  if (SKIP_FILE.test(e.name))
282
281
  continue;
282
+ // Collect all source files
283
+ result.allSourceFiles.push(p);
283
284
  // entry point detection
284
285
  if (!result.entry && (ENTRY_NAMES.has(e.name) || ENTRY_REGEX.test(e.name))) {
285
286
  result.entry = p;
@@ -856,33 +857,66 @@ INSTRUCCIONES GENERALES:
856
857
  }
857
858
  };
858
859
  // Role binaries (agent-orch, agent-impl, etc.) require an interactive TTY and can't
859
- // be spawned as subprocesses. Instead, if they have their own Qwen credentials
860
- // (~/.agent-<name>/oauth_creds.json from running `agent-<name> --login`), call
861
- // the Qwen API directly with those creds. Otherwise skip to fallback.
860
+ // be spawned as subprocesses. Instead, call the appropriate API directly
861
+ // (Qwen via oauth_creds.json, DeepSeek via deepseek_key.json).
862
862
  const ROLE_BINARIES = new Set(['agent-orch', 'agent-impl', 'agent-rev', 'agent-explorer']);
863
+ const DEEPSEEK_MODEL_PREFIX = /^deepseek-/;
863
864
  const tryRoleBinaryCreds = async (cliName, model) => {
864
- const credsPath = path.join(os.homedir(), `.${cliName}`, 'oauth_creds.json');
865
- const hasOAuthCreds = await fileExists(credsPath);
866
- if (!hasOAuthCreds) {
867
- // No role OAuth creds — try global API key config before giving up
865
+ const roleDir = path.join(os.homedir(), `.${cliName}`);
866
+ const credsPath = path.join(roleDir, 'oauth_creds.json');
867
+ const deepseekRoleKeyPath = path.join(roleDir, 'deepseek_key.json');
868
+ const deepseekGlobalKeyPath = path.join(os.homedir(), '.agent-mp', 'deepseek_key.json');
869
+ const isDeepSeekModel = DEEPSEEK_MODEL_PREFIX.test(model);
870
+ const hasQwenCreds = await fileExists(credsPath);
871
+ const hasDeepSeekCreds = await fileExists(deepseekRoleKeyPath) || await fileExists(deepseekGlobalKeyPath);
872
+ // Determine primary provider: DeepSeek model with DeepSeek creds -> DeepSeek, else Qwen
873
+ if (isDeepSeekModel && hasDeepSeekCreds) {
874
+ const sp = this._startSpinner(`${cliName} ${model} (deepseek)`);
875
+ const onChunk = (delta) => sp.push(delta);
876
+ try {
877
+ log.info(`${cliName}: calling DeepSeek API (${model})`);
878
+ const result = await callDeepSeekAPI(rolePrompt, model, onChunk);
879
+ sp.stop();
880
+ return result;
881
+ }
882
+ catch (err) {
883
+ sp.stop();
884
+ if (err.message?.startsWith('DEEPSEEK_NOT_CONFIGURED')) {
885
+ log.warn(`${cliName} DeepSeek not configured — using fallback`);
886
+ }
887
+ else if (err.message?.startsWith('DEEPSEEK_QUOTA_EXCEEDED')) {
888
+ log.warn(`${cliName} DeepSeek quota exhausted — using fallback`);
889
+ }
890
+ else if (err.message?.startsWith('DEEPSEEK_NO_BALANCE')) {
891
+ log.warn(`${cliName} DeepSeek balance insufficient — using fallback`);
892
+ }
893
+ else {
894
+ log.warn(`${cliName} DeepSeek API call failed: ${err.message}`);
895
+ }
896
+ return null; // don't try Qwen with a DeepSeek model — let caller fallback handle it
897
+ }
898
+ }
899
+ // Qwen path (for non-DeepSeek models only)
900
+ if (!hasQwenCreds) {
868
901
  const { loadApiKeyConfig } = await import('../utils/qwen-auth.js');
869
902
  const apiKeyCfg = await loadApiKeyConfig();
870
903
  if (!apiKeyCfg) {
871
- log.warn(`${cliName} has no credentials — run: agent-mp setup api-key or ${cliName} --login`);
904
+ if (!isDeepSeekModel) {
905
+ log.warn(`${cliName} has no credentials — run: agent-mp setup api-key or ${cliName} --login`);
906
+ }
872
907
  return null;
873
908
  }
874
- // Fall through: callQwenAPIFromCreds will use the API key config
875
909
  }
876
- const sp = this._startSpinner(`${cliName} ${model}`);
877
- const onChunk = (delta) => sp.push(delta);
910
+ const sp2 = this._startSpinner(`${cliName} ${model} ${isDeepSeekModel ? '(qwen fallback)' : ''}`);
911
+ const onChunk2 = (delta) => sp2.push(delta);
878
912
  try {
879
913
  log.info(`${cliName}: calling Qwen API with own credentials (${model})`);
880
- const result = await callQwenAPIFromCreds(rolePrompt, model, credsPath, onChunk);
881
- sp.stop();
914
+ const result = await callQwenAPIFromCreds(rolePrompt, model, credsPath, onChunk2);
915
+ sp2.stop();
882
916
  return result;
883
917
  }
884
918
  catch (err) {
885
- sp.stop();
919
+ sp2.stop();
886
920
  if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
887
921
  log.warn(`${cliName} session expired — using fallback`);
888
922
  return null;
@@ -1580,6 +1614,23 @@ INSTRUCCIONES:
1580
1614
  compSection.push(`### SCHEMA/MODEL: ${rel}\n\`\`\`\n${content}\n\`\`\``);
1581
1615
  }
1582
1616
  }
1617
+ // Read ALL source files for function-level documentation
1618
+ const MAX_SOURCE_FILE = 2000;
1619
+ const MAX_TOTAL_SOURCE = 20000;
1620
+ let totalSourceRead = 0;
1621
+ const alreadyRead = new Set([found.entry, ...found.controllers, ...found.schemas].filter(Boolean));
1622
+ for (const srcFile of found.allSourceFiles) {
1623
+ if (totalSourceRead >= MAX_TOTAL_SOURCE)
1624
+ break;
1625
+ if (alreadyRead.has(srcFile))
1626
+ continue;
1627
+ const content = await readFileSafe(srcFile, MAX_SOURCE_FILE);
1628
+ if (content) {
1629
+ const rel = path.relative(compDir, srcFile);
1630
+ compSection.push(`### SOURCE: ${rel}\n\`\`\`\n${content}\n\`\`\``);
1631
+ totalSourceRead += content.length;
1632
+ }
1633
+ }
1583
1634
  // Only add the component if we read at least the manifest or one source file
1584
1635
  if (manifestFound || found.entry || found.controllers.length > 0 || found.schemas.length > 0) {
1585
1636
  sections.push(compSection.join('\n\n'));
@@ -1774,7 +1825,7 @@ Lo concreto: acceso a hosts externos, herramientas requeridas, secrets esperados
1774
1825
  (scripts reales: package.json scripts, Makefile targets, run.sh, mvnw, gradlew, etc.)
1775
1826
 
1776
1827
 
1777
- ────── NIVEL 1 (.agent/context/<componente>/architecture.md, 100-220 lineas) ──────
1828
+ ────── NIVEL 1 (.agent/context/<componente>/architecture.md, 150-400 lineas) ──────
1778
1829
 
1779
1830
  # <componente> — Arquitectura
1780
1831
 
@@ -1806,6 +1857,44 @@ src/
1806
1857
  ├── ... → ...
1807
1858
  \`\`\`
1808
1859
 
1860
+ ## Flujo interno y funciones
1861
+
1862
+ REGLAS:
1863
+ - Lee cada archivo fuente del componente (no solo entry/controller, TODOS los .ts/.js/.py/.go/.java/.rs/.rb/.ex/.cs que haya).
1864
+ - Por cada archivo, lista sus funciones exportadas y las internas importantes.
1865
+ - NO escribas el codigo completo — solo descripcion resumida de que hace.
1866
+ - Para cada funcion, indica con que se conecta (mismo archivo, otro modulo, API externa, BD).
1867
+ - Inclui un ejemplo concreto de datos de entrada y salida.
1868
+
1869
+ ### <ruta/archivo.ext>
1870
+ | Funcion | Que hace | Recibe (ejemplo) | Devuelve (ejemplo) | Conecta con |
1871
+ |---------|----------|-------------------|---------------------|-------------|
1872
+ | nombreFunc | 1-linea que hace | {campo: valor, ...} | {campo: valor, ...} | modulo.func(), this.otra(), API externa |
1873
+
1874
+ #### Conecta con — leyenda
1875
+ | Notacion | Significado |
1876
+ |---|---|
1877
+ | \`this.func()\` | Mismo archivo |
1878
+ | \`Modulo.func()\` | Otro archivo del componente |
1879
+ | \`@comp/Otro.func()\` | Otro componente del proyecto |
1880
+ | \`HTTP POST /api/x\` | Llamada HTTP externa |
1881
+ | \`DB: tabla.columna\` | Base de datos |
1882
+ | \`COLA: nombre\` | Mensajeria / eventos |
1883
+
1884
+ ### Grafo de dependencias (obligatorio)
1885
+ Diagrama ASCII que muestre como se llaman las funciones entre si.
1886
+ Usa nombres reales de funciones, no de archivos.
1887
+ \`\`\`text
1888
+ controlador.createUser(req)
1889
+
1890
+ ├──> UserService.validate(data) ── mismo archivo
1891
+ │ └──> UserModel.findByEmail(e) ── DB
1892
+
1893
+ ├──> UserModel.create(user) ── DB: users.insert
1894
+
1895
+ └──> EmailService.sendWelcome(email) ── @notification/EmailService.send()
1896
+ \`\`\`
1897
+
1809
1898
  ## Endpoints / Rutas / Pantallas
1810
1899
  - Backend (api): tabla con metodo, ruta, auth, proposito.
1811
1900
  - Frontend (web): tabla con ruta de pagina, proposito, datos que consume.
@@ -10,5 +10,5 @@ export declare function deepseekAuthStatus(): Promise<{
10
10
  model?: string;
11
11
  }>;
12
12
  export declare function callDeepSeekAPI(prompt: string, model?: string, onData?: (chunk: string) => void): Promise<string>;
13
+ export declare const DEEPSEEK_DEFAULT_MODELS: string[];
13
14
  export declare function fetchDeepSeekModels(cfg?: DeepSeekKeyConfig | null): Promise<string[]>;
14
- export declare const DEEPSEEK_MODELS: string[];
@@ -35,6 +35,7 @@ export async function callDeepSeekAPI(prompt, model = 'deepseek-chat', onData) {
35
35
  throw new Error('DEEPSEEK_NOT_CONFIGURED: Run /login y elegí DeepSeek');
36
36
  }
37
37
  const useModel = (model && model !== 'deepseek-model') ? model : cfg.model;
38
+ const isV4 = useModel.startsWith('deepseek-v4');
38
39
  const client = new OpenAI({
39
40
  apiKey: cfg.api_key,
40
41
  baseURL: DEEPSEEK_BASE_URL,
@@ -43,21 +44,26 @@ export async function callDeepSeekAPI(prompt, model = 'deepseek-chat', onData) {
43
44
  let lastErr;
44
45
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
45
46
  try {
47
+ const baseParams = {
48
+ model: useModel,
49
+ messages: [{ role: 'user', content: prompt }],
50
+ };
51
+ if (isV4) {
52
+ baseParams.reasoning_effort = 'high';
53
+ baseParams.extra_body = { thinking: { type: 'enabled' } };
54
+ }
46
55
  if (!onData) {
47
- const completion = await client.chat.completions.create({
48
- model: useModel,
49
- messages: [{ role: 'user', content: prompt }],
50
- });
56
+ const completion = await client.chat.completions.create(baseParams);
51
57
  return completion.choices[0]?.message?.content ?? '';
52
58
  }
53
59
  let fullText = '';
54
60
  const stream = await client.chat.completions.create({
55
- model: useModel,
56
- messages: [{ role: 'user', content: prompt }],
61
+ ...baseParams,
57
62
  stream: true,
63
+ stream_options: { include_usage: true },
58
64
  });
59
65
  for await (const chunk of stream) {
60
- const delta = chunk.choices[0]?.delta?.content ?? '';
66
+ const delta = chunk.choices?.[0]?.delta?.content ?? '';
61
67
  if (delta) {
62
68
  fullText += delta;
63
69
  onData(delta);
@@ -88,6 +94,7 @@ export async function callDeepSeekAPI(prompt, model = 'deepseek-chat', onData) {
88
94
  }
89
95
  throw lastErr;
90
96
  }
97
+ export const DEEPSEEK_DEFAULT_MODELS = ['deepseek-chat', 'deepseek-reasoner'];
91
98
  export async function fetchDeepSeekModels(cfg) {
92
99
  const resolved = cfg ?? await loadDeepSeekApiKey();
93
100
  if (!resolved?.api_key)
@@ -101,7 +108,3 @@ export async function fetchDeepSeekModels(cfg) {
101
108
  return [];
102
109
  }
103
110
  }
104
- export const DEEPSEEK_MODELS = [
105
- 'deepseek-chat',
106
- 'deepseek-reasoner',
107
- ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-mp",
3
- "version": "0.5.40",
3
+ "version": "0.5.42",
4
4
  "description": "Deterministic multi-agent CLI orchestrator — plan, code, review",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",