agent-rev 0.5.40 → 0.5.43
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 +10 -5
- package/dist/core/engine.js +134 -19
- package/dist/utils/deepseek.d.ts +1 -1
- package/dist/utils/deepseek.js +14 -11
- package/package.json +1 -1
package/dist/commands/repl.js
CHANGED
|
@@ -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,
|
|
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
|
|
420
|
-
|
|
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
|
-
|
|
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 =
|
|
813
|
+
models = DEEPSEEK_DEFAULT_MODELS;
|
|
809
814
|
}
|
|
810
815
|
else if (activeProvider === 'qwen') {
|
|
811
816
|
console.log(chalk.dim(' Fetching Qwen models...'));
|
package/dist/core/engine.js
CHANGED
|
@@ -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;
|
|
@@ -350,6 +351,8 @@ export class AgentEngine {
|
|
|
350
351
|
const t0 = Date.now();
|
|
351
352
|
const fi = this.fi;
|
|
352
353
|
let streaming = false;
|
|
354
|
+
let statusLine = '';
|
|
355
|
+
let buf = '';
|
|
353
356
|
fi.startActivity(`${frames[0]} ${label} 0s`);
|
|
354
357
|
fi.setActivityLines([` ${dotFrames[0]} esperando respuesta...`]);
|
|
355
358
|
const iv = setInterval(() => {
|
|
@@ -359,10 +362,34 @@ export class AgentEngine {
|
|
|
359
362
|
if (!streaming) {
|
|
360
363
|
fi.setActivityLines([` ${dotFrames[ti % dotFrames.length]} esperando respuesta...`]);
|
|
361
364
|
}
|
|
365
|
+
else if (statusLine) {
|
|
366
|
+
fi.setActivityLines([statusLine]);
|
|
367
|
+
}
|
|
362
368
|
}, 300);
|
|
363
369
|
return {
|
|
364
370
|
stop() { clearInterval(iv); fi.stopActivity(); },
|
|
365
|
-
push(
|
|
371
|
+
push(chunk) {
|
|
372
|
+
streaming = true;
|
|
373
|
+
buf += chunk;
|
|
374
|
+
// Detect === file.md === markers — show which file is being written
|
|
375
|
+
const markers = [...buf.matchAll(/===\s*([\w./\-]+\.md)\s*===/g)];
|
|
376
|
+
if (markers.length > 0) {
|
|
377
|
+
statusLine = ` → ${markers[markers.length - 1][1]}`;
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
// Show last meaningful line of the streamed text
|
|
381
|
+
const lines = buf.split('\n');
|
|
382
|
+
for (let j = lines.length - 1; j >= 0; j--) {
|
|
383
|
+
const l = lines[j].trim();
|
|
384
|
+
if (l.length > 3 && !l.startsWith('```') && !l.startsWith('|---') && !l.startsWith('#')) {
|
|
385
|
+
statusLine = ` ${l.slice(0, 72)}`;
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (buf.length > 6000)
|
|
391
|
+
buf = buf.slice(-3000);
|
|
392
|
+
},
|
|
366
393
|
};
|
|
367
394
|
}
|
|
368
395
|
/** Extract readable text lines from a qwen/CLI streaming chunk. */
|
|
@@ -856,33 +883,66 @@ INSTRUCCIONES GENERALES:
|
|
|
856
883
|
}
|
|
857
884
|
};
|
|
858
885
|
// Role binaries (agent-orch, agent-impl, etc.) require an interactive TTY and can't
|
|
859
|
-
// be spawned as subprocesses. Instead,
|
|
860
|
-
// (
|
|
861
|
-
// the Qwen API directly with those creds. Otherwise skip to fallback.
|
|
886
|
+
// be spawned as subprocesses. Instead, call the appropriate API directly
|
|
887
|
+
// (Qwen via oauth_creds.json, DeepSeek via deepseek_key.json).
|
|
862
888
|
const ROLE_BINARIES = new Set(['agent-orch', 'agent-impl', 'agent-rev', 'agent-explorer']);
|
|
889
|
+
const DEEPSEEK_MODEL_PREFIX = /^deepseek-/;
|
|
863
890
|
const tryRoleBinaryCreds = async (cliName, model) => {
|
|
864
|
-
const
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
891
|
+
const roleDir = path.join(os.homedir(), `.${cliName}`);
|
|
892
|
+
const credsPath = path.join(roleDir, 'oauth_creds.json');
|
|
893
|
+
const deepseekRoleKeyPath = path.join(roleDir, 'deepseek_key.json');
|
|
894
|
+
const deepseekGlobalKeyPath = path.join(os.homedir(), '.agent-mp', 'deepseek_key.json');
|
|
895
|
+
const isDeepSeekModel = DEEPSEEK_MODEL_PREFIX.test(model);
|
|
896
|
+
const hasQwenCreds = await fileExists(credsPath);
|
|
897
|
+
const hasDeepSeekCreds = await fileExists(deepseekRoleKeyPath) || await fileExists(deepseekGlobalKeyPath);
|
|
898
|
+
// Determine primary provider: DeepSeek model with DeepSeek creds -> DeepSeek, else Qwen
|
|
899
|
+
if (isDeepSeekModel && hasDeepSeekCreds) {
|
|
900
|
+
const sp = this._startSpinner(`${cliName} ${model} (deepseek)`);
|
|
901
|
+
const onChunk = (delta) => sp.push(delta);
|
|
902
|
+
try {
|
|
903
|
+
log.info(`${cliName}: calling DeepSeek API (${model})`);
|
|
904
|
+
const result = await callDeepSeekAPI(rolePrompt, model, onChunk);
|
|
905
|
+
sp.stop();
|
|
906
|
+
return result;
|
|
907
|
+
}
|
|
908
|
+
catch (err) {
|
|
909
|
+
sp.stop();
|
|
910
|
+
if (err.message?.startsWith('DEEPSEEK_NOT_CONFIGURED')) {
|
|
911
|
+
log.warn(`${cliName} DeepSeek not configured — using fallback`);
|
|
912
|
+
}
|
|
913
|
+
else if (err.message?.startsWith('DEEPSEEK_QUOTA_EXCEEDED')) {
|
|
914
|
+
log.warn(`${cliName} DeepSeek quota exhausted — using fallback`);
|
|
915
|
+
}
|
|
916
|
+
else if (err.message?.startsWith('DEEPSEEK_NO_BALANCE')) {
|
|
917
|
+
log.warn(`${cliName} DeepSeek balance insufficient — using fallback`);
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
log.warn(`${cliName} DeepSeek API call failed: ${err.message}`);
|
|
921
|
+
}
|
|
922
|
+
return null; // don't try Qwen with a DeepSeek model — let caller fallback handle it
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
// Qwen path (for non-DeepSeek models only)
|
|
926
|
+
if (!hasQwenCreds) {
|
|
868
927
|
const { loadApiKeyConfig } = await import('../utils/qwen-auth.js');
|
|
869
928
|
const apiKeyCfg = await loadApiKeyConfig();
|
|
870
929
|
if (!apiKeyCfg) {
|
|
871
|
-
|
|
930
|
+
if (!isDeepSeekModel) {
|
|
931
|
+
log.warn(`${cliName} has no credentials — run: agent-mp setup api-key or ${cliName} --login`);
|
|
932
|
+
}
|
|
872
933
|
return null;
|
|
873
934
|
}
|
|
874
|
-
// Fall through: callQwenAPIFromCreds will use the API key config
|
|
875
935
|
}
|
|
876
|
-
const
|
|
877
|
-
const
|
|
936
|
+
const sp2 = this._startSpinner(`${cliName} ${model} ${isDeepSeekModel ? '(qwen fallback)' : ''}`);
|
|
937
|
+
const onChunk2 = (delta) => sp2.push(delta);
|
|
878
938
|
try {
|
|
879
939
|
log.info(`${cliName}: calling Qwen API with own credentials (${model})`);
|
|
880
|
-
const result = await callQwenAPIFromCreds(rolePrompt, model, credsPath,
|
|
881
|
-
|
|
940
|
+
const result = await callQwenAPIFromCreds(rolePrompt, model, credsPath, onChunk2);
|
|
941
|
+
sp2.stop();
|
|
882
942
|
return result;
|
|
883
943
|
}
|
|
884
944
|
catch (err) {
|
|
885
|
-
|
|
945
|
+
sp2.stop();
|
|
886
946
|
if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
|
|
887
947
|
log.warn(`${cliName} session expired — using fallback`);
|
|
888
948
|
return null;
|
|
@@ -1580,6 +1640,23 @@ INSTRUCCIONES:
|
|
|
1580
1640
|
compSection.push(`### SCHEMA/MODEL: ${rel}\n\`\`\`\n${content}\n\`\`\``);
|
|
1581
1641
|
}
|
|
1582
1642
|
}
|
|
1643
|
+
// Read ALL source files for function-level documentation
|
|
1644
|
+
const MAX_SOURCE_FILE = 2000;
|
|
1645
|
+
const MAX_TOTAL_SOURCE = 20000;
|
|
1646
|
+
let totalSourceRead = 0;
|
|
1647
|
+
const alreadyRead = new Set([found.entry, ...found.controllers, ...found.schemas].filter(Boolean));
|
|
1648
|
+
for (const srcFile of found.allSourceFiles) {
|
|
1649
|
+
if (totalSourceRead >= MAX_TOTAL_SOURCE)
|
|
1650
|
+
break;
|
|
1651
|
+
if (alreadyRead.has(srcFile))
|
|
1652
|
+
continue;
|
|
1653
|
+
const content = await readFileSafe(srcFile, MAX_SOURCE_FILE);
|
|
1654
|
+
if (content) {
|
|
1655
|
+
const rel = path.relative(compDir, srcFile);
|
|
1656
|
+
compSection.push(`### SOURCE: ${rel}\n\`\`\`\n${content}\n\`\`\``);
|
|
1657
|
+
totalSourceRead += content.length;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1583
1660
|
// Only add the component if we read at least the manifest or one source file
|
|
1584
1661
|
if (manifestFound || found.entry || found.controllers.length > 0 || found.schemas.length > 0) {
|
|
1585
1662
|
sections.push(compSection.join('\n\n'));
|
|
@@ -1774,7 +1851,7 @@ Lo concreto: acceso a hosts externos, herramientas requeridas, secrets esperados
|
|
|
1774
1851
|
(scripts reales: package.json scripts, Makefile targets, run.sh, mvnw, gradlew, etc.)
|
|
1775
1852
|
|
|
1776
1853
|
|
|
1777
|
-
────── NIVEL 1 (.agent/context/<componente>/architecture.md,
|
|
1854
|
+
────── NIVEL 1 (.agent/context/<componente>/architecture.md, 150-400 lineas) ──────
|
|
1778
1855
|
|
|
1779
1856
|
# <componente> — Arquitectura
|
|
1780
1857
|
|
|
@@ -1806,6 +1883,44 @@ src/
|
|
|
1806
1883
|
├── ... → ...
|
|
1807
1884
|
\`\`\`
|
|
1808
1885
|
|
|
1886
|
+
## Flujo interno y funciones
|
|
1887
|
+
|
|
1888
|
+
REGLAS:
|
|
1889
|
+
- Lee cada archivo fuente del componente (no solo entry/controller, TODOS los .ts/.js/.py/.go/.java/.rs/.rb/.ex/.cs que haya).
|
|
1890
|
+
- Por cada archivo, lista sus funciones exportadas y las internas importantes.
|
|
1891
|
+
- NO escribas el codigo completo — solo descripcion resumida de que hace.
|
|
1892
|
+
- Para cada funcion, indica con que se conecta (mismo archivo, otro modulo, API externa, BD).
|
|
1893
|
+
- Inclui un ejemplo concreto de datos de entrada y salida.
|
|
1894
|
+
|
|
1895
|
+
### <ruta/archivo.ext>
|
|
1896
|
+
| Funcion | Que hace | Recibe (ejemplo) | Devuelve (ejemplo) | Conecta con |
|
|
1897
|
+
|---------|----------|-------------------|---------------------|-------------|
|
|
1898
|
+
| nombreFunc | 1-linea que hace | {campo: valor, ...} | {campo: valor, ...} | modulo.func(), this.otra(), API externa |
|
|
1899
|
+
|
|
1900
|
+
#### Conecta con — leyenda
|
|
1901
|
+
| Notacion | Significado |
|
|
1902
|
+
|---|---|
|
|
1903
|
+
| \`this.func()\` | Mismo archivo |
|
|
1904
|
+
| \`Modulo.func()\` | Otro archivo del componente |
|
|
1905
|
+
| \`@comp/Otro.func()\` | Otro componente del proyecto |
|
|
1906
|
+
| \`HTTP POST /api/x\` | Llamada HTTP externa |
|
|
1907
|
+
| \`DB: tabla.columna\` | Base de datos |
|
|
1908
|
+
| \`COLA: nombre\` | Mensajeria / eventos |
|
|
1909
|
+
|
|
1910
|
+
### Grafo de dependencias (obligatorio)
|
|
1911
|
+
Diagrama ASCII que muestre como se llaman las funciones entre si.
|
|
1912
|
+
Usa nombres reales de funciones, no de archivos.
|
|
1913
|
+
\`\`\`text
|
|
1914
|
+
controlador.createUser(req)
|
|
1915
|
+
│
|
|
1916
|
+
├──> UserService.validate(data) ── mismo archivo
|
|
1917
|
+
│ └──> UserModel.findByEmail(e) ── DB
|
|
1918
|
+
│
|
|
1919
|
+
├──> UserModel.create(user) ── DB: users.insert
|
|
1920
|
+
│
|
|
1921
|
+
└──> EmailService.sendWelcome(email) ── @notification/EmailService.send()
|
|
1922
|
+
\`\`\`
|
|
1923
|
+
|
|
1809
1924
|
## Endpoints / Rutas / Pantallas
|
|
1810
1925
|
- Backend (api): tabla con metodo, ruta, auth, proposito.
|
|
1811
1926
|
- Frontend (web): tabla con ruta de pagina, proposito, datos que consume.
|
package/dist/utils/deepseek.d.ts
CHANGED
|
@@ -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[];
|
package/dist/utils/deepseek.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
];
|