chocolatito-code 1.0.0
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/.mcp.json.ejemplo +19 -0
- package/COMPUTER-USE.md +484 -0
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/agent/context.d.ts +9 -0
- package/dist/agent/context.js +123 -0
- package/dist/agent/fileTracker.d.ts +16 -0
- package/dist/agent/fileTracker.js +53 -0
- package/dist/agent/loop.d.ts +25 -0
- package/dist/agent/loop.js +524 -0
- package/dist/agent/mentions.d.ts +11 -0
- package/dist/agent/mentions.js +43 -0
- package/dist/agent/retry.d.ts +17 -0
- package/dist/agent/retry.js +57 -0
- package/dist/agent/subagent.d.ts +2 -0
- package/dist/agent/subagent.js +116 -0
- package/dist/agent/syntaxValidator.d.ts +5 -0
- package/dist/agent/syntaxValidator.js +71 -0
- package/dist/agent/tracker.d.ts +19 -0
- package/dist/agent/tracker.js +38 -0
- package/dist/agent/undoManager.d.ts +37 -0
- package/dist/agent/undoManager.js +95 -0
- package/dist/config/constants.d.ts +77 -0
- package/dist/config/constants.js +146 -0
- package/dist/config/engine.d.ts +49 -0
- package/dist/config/engine.js +86 -0
- package/dist/config/env.d.ts +13 -0
- package/dist/config/env.js +103 -0
- package/dist/config/license.d.ts +39 -0
- package/dist/config/license.js +306 -0
- package/dist/config/permissions.d.ts +38 -0
- package/dist/config/permissions.js +239 -0
- package/dist/config/quota.d.ts +58 -0
- package/dist/config/quota.js +156 -0
- package/dist/hooks/manager.d.ts +60 -0
- package/dist/hooks/manager.js +193 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +470 -0
- package/dist/mcp/client.d.ts +55 -0
- package/dist/mcp/client.js +282 -0
- package/dist/mcp/manager.d.ts +26 -0
- package/dist/mcp/manager.js +167 -0
- package/dist/memory/manager.d.ts +9 -0
- package/dist/memory/manager.js +59 -0
- package/dist/prompts/systemPrompt.d.ts +5 -0
- package/dist/prompts/systemPrompt.js +177 -0
- package/dist/sessions/manager.d.ts +21 -0
- package/dist/sessions/manager.js +54 -0
- package/dist/skills/manager.d.ts +14 -0
- package/dist/skills/manager.js +86 -0
- package/dist/tools/browserCdp.d.ts +26 -0
- package/dist/tools/browserCdp.js +610 -0
- package/dist/tools/browserExtension.d.ts +58 -0
- package/dist/tools/browserExtension.js +479 -0
- package/dist/tools/browserLive.d.ts +12 -0
- package/dist/tools/browserLive.js +265 -0
- package/dist/tools/changeDir.d.ts +5 -0
- package/dist/tools/changeDir.js +63 -0
- package/dist/tools/computerUse.d.ts +58 -0
- package/dist/tools/computerUse.js +567 -0
- package/dist/tools/createImage.d.ts +1 -0
- package/dist/tools/createImage.js +34 -0
- package/dist/tools/definitions.d.ts +8 -0
- package/dist/tools/definitions.js +401 -0
- package/dist/tools/deleteFile.d.ts +1 -0
- package/dist/tools/deleteFile.js +17 -0
- package/dist/tools/editFile.d.ts +5 -0
- package/dist/tools/editFile.js +88 -0
- package/dist/tools/findFiles.d.ts +1 -0
- package/dist/tools/findFiles.js +33 -0
- package/dist/tools/getSystemInfo.d.ts +1 -0
- package/dist/tools/getSystemInfo.js +23 -0
- package/dist/tools/gitTools.d.ts +3 -0
- package/dist/tools/gitTools.js +47 -0
- package/dist/tools/grepSearch.d.ts +1 -0
- package/dist/tools/grepSearch.js +37 -0
- package/dist/tools/listDir.d.ts +1 -0
- package/dist/tools/listDir.js +33 -0
- package/dist/tools/moveCopyFile.d.ts +1 -0
- package/dist/tools/moveCopyFile.js +21 -0
- package/dist/tools/runCommand.d.ts +1 -0
- package/dist/tools/runCommand.js +61 -0
- package/dist/tools/runner.d.ts +5 -0
- package/dist/tools/runner.js +177 -0
- package/dist/tools/safety.d.ts +17 -0
- package/dist/tools/safety.js +33 -0
- package/dist/tools/todoTool.d.ts +8 -0
- package/dist/tools/todoTool.js +27 -0
- package/dist/tools/toolDefsComputer.d.ts +13 -0
- package/dist/tools/toolDefsComputer.js +205 -0
- package/dist/tools/truncator.d.ts +11 -0
- package/dist/tools/truncator.js +24 -0
- package/dist/tools/viewFile.d.ts +1 -0
- package/dist/tools/viewFile.js +45 -0
- package/dist/tools/visionBridge.d.ts +37 -0
- package/dist/tools/visionBridge.js +107 -0
- package/dist/tools/webFetch.d.ts +1 -0
- package/dist/tools/webFetch.js +25 -0
- package/dist/tools/webSearch.d.ts +1 -0
- package/dist/tools/webSearch.js +35 -0
- package/dist/tools/win/hostClient.d.ts +31 -0
- package/dist/tools/win/hostClient.js +158 -0
- package/dist/tools/win/hostScript.d.ts +1 -0
- package/dist/tools/win/hostScript.js +925 -0
- package/dist/tools/writeFile.d.ts +1 -0
- package/dist/tools/writeFile.js +13 -0
- package/dist/ui/banner.d.ts +10 -0
- package/dist/ui/banner.js +165 -0
- package/dist/ui/diffViewer.d.ts +1 -0
- package/dist/ui/diffViewer.js +60 -0
- package/dist/ui/interrupt.d.ts +17 -0
- package/dist/ui/interrupt.js +156 -0
- package/dist/ui/markdownStream.d.ts +23 -0
- package/dist/ui/markdownStream.js +0 -0
- package/dist/ui/modes.d.ts +28 -0
- package/dist/ui/modes.js +57 -0
- package/dist/ui/permissionPrompt.d.ts +35 -0
- package/dist/ui/permissionPrompt.js +271 -0
- package/dist/ui/prompt.d.ts +7 -0
- package/dist/ui/prompt.js +466 -0
- package/dist/ui/reasoningStream.d.ts +6 -0
- package/dist/ui/reasoningStream.js +38 -0
- package/dist/ui/renderer.d.ts +10 -0
- package/dist/ui/renderer.js +142 -0
- package/dist/ui/spinner.d.ts +24 -0
- package/dist/ui/spinner.js +111 -0
- package/dist/ui/theme.d.ts +36 -0
- package/dist/ui/theme.js +37 -0
- package/dist/ui/typeAhead.d.ts +31 -0
- package/dist/ui/typeAhead.js +87 -0
- package/extension/README.md +74 -0
- package/extension/background.js +575 -0
- package/extension/content.js +446 -0
- package/extension/manifest.json +36 -0
- package/extension/popup.html +22 -0
- package/extension/popup.js +12 -0
- package/package.json +66 -0
- package/skills/complex-refactor/SKILL.md +13 -0
- package/skills/deep-debugger/SKILL.md +14 -0
- package/skills/software-architect/SKILL.md +20 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { TOOLS, getAllTools } from "../tools/definitions.js";
|
|
2
|
+
import { executeToolCall } from "../tools/runner.js";
|
|
3
|
+
import { truncateOutput } from "../tools/truncator.js";
|
|
4
|
+
import { isReadOnly } from "../tools/safety.js";
|
|
5
|
+
import { MAX_OUTPUT_TOKENS } from "../config/constants.js";
|
|
6
|
+
import { withRetry } from "./retry.js";
|
|
7
|
+
import { createEngineClient } from "../config/engine.js";
|
|
8
|
+
/**
|
|
9
|
+
* Subagentes.
|
|
10
|
+
*
|
|
11
|
+
* Tres arreglos sobre la version anterior:
|
|
12
|
+
*
|
|
13
|
+
* 1. 'explore' y 'plan' recibian TODAS las herramientas, incluidas delete_file,
|
|
14
|
+
* write_file y run_command. Un agente que se presenta como "de solo lectura"
|
|
15
|
+
* y puede borrar archivos es una trampa: ahora se le entregan solo las
|
|
16
|
+
* herramientas de lectura, asi la restriccion es real y no una sugerencia
|
|
17
|
+
* en el prompt.
|
|
18
|
+
*
|
|
19
|
+
* 2. Si agotaba las iteraciones devolvia "Subagente completo la ejecucion", que
|
|
20
|
+
* no dice nada. Todo el trabajo se perdia. Ahora se le pide expresamente un
|
|
21
|
+
* resumen de lo averiguado antes de rendirse.
|
|
22
|
+
*
|
|
23
|
+
* 3. Sin reintentos: un 429 mataba al subagente y con el la tarea del padre.
|
|
24
|
+
*/
|
|
25
|
+
const READ_ONLY_TYPES = ["explore", "plan"];
|
|
26
|
+
function toolsFor(type) {
|
|
27
|
+
// Un subagente general hereda tambien las herramientas MCP; uno de solo
|
|
28
|
+
// lectura no, porque no se puede saber si una herramienta MCP escribe.
|
|
29
|
+
if (!READ_ONLY_TYPES.includes(type))
|
|
30
|
+
return getAllTools();
|
|
31
|
+
return TOOLS.filter((t) => isReadOnly(t.function.name));
|
|
32
|
+
}
|
|
33
|
+
export async function runSubagent(type, prompt, apiKey, baseURL, modelName, cwd = process.cwd()) {
|
|
34
|
+
const client = createEngineClient(apiKey, "subagent");
|
|
35
|
+
const roleInstructions = {
|
|
36
|
+
explore: `Eres un SUBAGENTE EXPLORADOR de Chocolatito Code. Investigas archivos y buscas código, y devuelves un informe conciso con rutas y números de línea concretos. Solo tienes herramientas de lectura: no puedes modificar nada.`,
|
|
37
|
+
plan: `Eres un SUBAGENTE ARQUITECTO de Chocolatito Code. Analizas el código existente y devuelves un plan paso a paso con los archivos a crear o modificar y por qué. Solo tienes herramientas de lectura: no implementes, planifica.`,
|
|
38
|
+
general: `Eres un SUBAGENTE DE TRABAJO de Chocolatito Code. Completas la subtarea encomendada con las herramientas necesarias y devuelves el resultado final verificado.`,
|
|
39
|
+
};
|
|
40
|
+
const messages = [
|
|
41
|
+
{
|
|
42
|
+
role: "system",
|
|
43
|
+
content: `${roleInstructions[type]}\n\n` +
|
|
44
|
+
`Directorio de trabajo: ${cwd}\n` +
|
|
45
|
+
`Sistema: ${process.platform}\n\n` +
|
|
46
|
+
`Cuando necesites varias lecturas independientes, pidelas todas en el mismo turno: se ejecutan a la vez.\n` +
|
|
47
|
+
`Responde con hechos que hayas comprobado, citando rutas y lineas. No afirmes nada que no hayas leido.`,
|
|
48
|
+
},
|
|
49
|
+
{ role: "user", content: prompt },
|
|
50
|
+
];
|
|
51
|
+
const tools = toolsFor(type);
|
|
52
|
+
const maxIterations = 14;
|
|
53
|
+
for (let iterations = 0; iterations < maxIterations; iterations++) {
|
|
54
|
+
// Al acercarse al limite se avisa, para que cierre en vez de que le corten.
|
|
55
|
+
if (iterations === maxIterations - 2) {
|
|
56
|
+
messages.push({
|
|
57
|
+
role: "system",
|
|
58
|
+
content: "Aviso: te queda 1 iteración. Devuelve ya tu informe con lo que hayas averiguado.",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
let res;
|
|
62
|
+
try {
|
|
63
|
+
res = await withRetry(() => client.chat.completions.create({
|
|
64
|
+
model: modelName,
|
|
65
|
+
messages,
|
|
66
|
+
tools,
|
|
67
|
+
tool_choice: "auto",
|
|
68
|
+
temperature: 0.1,
|
|
69
|
+
max_tokens: MAX_OUTPUT_TOKENS,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return `Error en subagente (${type}): ${err?.message || String(err)}`;
|
|
74
|
+
}
|
|
75
|
+
const message = res.choices[0]?.message;
|
|
76
|
+
if (!message)
|
|
77
|
+
break;
|
|
78
|
+
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
79
|
+
messages.push(message);
|
|
80
|
+
// Las llamadas de un subagente explorador son todas de lectura: van a la vez.
|
|
81
|
+
const results = await Promise.all(message.tool_calls.map(async (tc) => {
|
|
82
|
+
let args = {};
|
|
83
|
+
try {
|
|
84
|
+
args = JSON.parse(tc.function.arguments);
|
|
85
|
+
}
|
|
86
|
+
catch { }
|
|
87
|
+
const { result } = await executeToolCall(tc.function.name, args, cwd, apiKey, modelName);
|
|
88
|
+
return { id: tc.id, content: truncateOutput(result, 20_000) };
|
|
89
|
+
}));
|
|
90
|
+
for (const r of results) {
|
|
91
|
+
messages.push({ role: "tool", tool_call_id: r.id, content: r.content });
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (message.content)
|
|
96
|
+
return message.content;
|
|
97
|
+
}
|
|
98
|
+
// Se agotaron las iteraciones: en vez de tirar el trabajo, se pide el resumen.
|
|
99
|
+
try {
|
|
100
|
+
messages.push({
|
|
101
|
+
role: "user",
|
|
102
|
+
content: "Se acabaron las iteraciones. Resume AHORA, sin usar herramientas, todo lo que has averiguado.",
|
|
103
|
+
});
|
|
104
|
+
const final = await withRetry(() => client.chat.completions.create({
|
|
105
|
+
model: modelName,
|
|
106
|
+
messages,
|
|
107
|
+
temperature: 0.1,
|
|
108
|
+
max_tokens: MAX_OUTPUT_TOKENS,
|
|
109
|
+
}));
|
|
110
|
+
const text = final.choices[0]?.message?.content;
|
|
111
|
+
if (text)
|
|
112
|
+
return `[Subagente ${type}: limite de iteraciones alcanzado]\n\n${text}`;
|
|
113
|
+
}
|
|
114
|
+
catch { }
|
|
115
|
+
return `El subagente ${type} agotó sus iteraciones sin llegar a una conclusión. Acota más la subtarea.`;
|
|
116
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export function validateFileSyntax(filePath, content) {
|
|
3
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
4
|
+
// 1. JSON validation
|
|
5
|
+
if (ext === ".json") {
|
|
6
|
+
try {
|
|
7
|
+
JSON.parse(content);
|
|
8
|
+
return { valid: true };
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
return {
|
|
12
|
+
valid: false,
|
|
13
|
+
warning: `⚠️ Advertencia de sintaxis JSON: ${err?.message || "JSON inválido"}`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// 2. Matching brackets & braces check for JS/TS/Python
|
|
18
|
+
if ([".ts", ".js", ".tsx", ".jsx", ".py", ".rs", ".go"].includes(ext)) {
|
|
19
|
+
const stack = [];
|
|
20
|
+
const pairs = { "}": "{", "]": "[", ")": "(" };
|
|
21
|
+
// Ignore comments & strings roughly to avoid false positives
|
|
22
|
+
let inString = null;
|
|
23
|
+
let inComment = false;
|
|
24
|
+
for (let i = 0; i < content.length; i++) {
|
|
25
|
+
const char = content[i];
|
|
26
|
+
const nextChar = content[i + 1];
|
|
27
|
+
// Comments
|
|
28
|
+
if (!inString) {
|
|
29
|
+
if (char === "/" && nextChar === "/") {
|
|
30
|
+
while (i < content.length && content[i] !== "\n")
|
|
31
|
+
i++;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (char === "#" && ext === ".py") {
|
|
35
|
+
while (i < content.length && content[i] !== "\n")
|
|
36
|
+
i++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Strings
|
|
41
|
+
if ((char === '"' || char === "'" || char === "`") && content[i - 1] !== "\\") {
|
|
42
|
+
if (!inString)
|
|
43
|
+
inString = char;
|
|
44
|
+
else if (inString === char)
|
|
45
|
+
inString = null;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (inString)
|
|
49
|
+
continue;
|
|
50
|
+
if (["{", "[", "("].includes(char)) {
|
|
51
|
+
stack.push(char);
|
|
52
|
+
}
|
|
53
|
+
else if (["}", "]", ")"].includes(char)) {
|
|
54
|
+
const expected = pairs[char];
|
|
55
|
+
if (stack.length === 0 || stack.pop() !== expected) {
|
|
56
|
+
return {
|
|
57
|
+
valid: false,
|
|
58
|
+
warning: `⚠️ Posible desbalance de llaves/paréntesis en "${path.basename(filePath)}" cerca del cierre '${char}'.`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (stack.length > 0 && stack.length < 5) {
|
|
64
|
+
return {
|
|
65
|
+
valid: false,
|
|
66
|
+
warning: `⚠️ Posible llave o paréntesis sin cerrar en "${path.basename(filePath)}" ('${stack.join("', '")}').`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { valid: true };
|
|
71
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare class TokenTracker {
|
|
2
|
+
totalPromptTokens: number;
|
|
3
|
+
totalCompletionTokens: number;
|
|
4
|
+
cachedPromptTokens: number;
|
|
5
|
+
addUsage(usage?: {
|
|
6
|
+
prompt_tokens?: number;
|
|
7
|
+
completion_tokens?: number;
|
|
8
|
+
prompt_tokens_details?: {
|
|
9
|
+
cached_tokens?: number;
|
|
10
|
+
};
|
|
11
|
+
}): void;
|
|
12
|
+
calculateCost(promptTokens?: number, cachedTokens?: number, completionTokens?: number, modelName?: string): {
|
|
13
|
+
inputCost: number;
|
|
14
|
+
outputCost: number;
|
|
15
|
+
totalCost: number;
|
|
16
|
+
formattedCost: string;
|
|
17
|
+
};
|
|
18
|
+
getSummary(modelName?: string): string;
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { MODEL_PRICING, PRICING } from "../config/constants.js";
|
|
2
|
+
export class TokenTracker {
|
|
3
|
+
totalPromptTokens = 0;
|
|
4
|
+
totalCompletionTokens = 0;
|
|
5
|
+
cachedPromptTokens = 0;
|
|
6
|
+
addUsage(usage) {
|
|
7
|
+
if (!usage)
|
|
8
|
+
return;
|
|
9
|
+
const prompt = usage.prompt_tokens || 0;
|
|
10
|
+
const completion = usage.completion_tokens || 0;
|
|
11
|
+
const cached = usage.prompt_tokens_details?.cached_tokens || 0;
|
|
12
|
+
this.totalPromptTokens += prompt;
|
|
13
|
+
this.totalCompletionTokens += completion;
|
|
14
|
+
this.cachedPromptTokens += cached;
|
|
15
|
+
}
|
|
16
|
+
calculateCost(promptTokens = this.totalPromptTokens, cachedTokens = this.cachedPromptTokens, completionTokens = this.totalCompletionTokens, modelName = "deepseek-v4-flash") {
|
|
17
|
+
const pricing = MODEL_PRICING[modelName] || PRICING;
|
|
18
|
+
const uncachedPrompt = Math.max(0, promptTokens - cachedTokens);
|
|
19
|
+
const inputCost = (uncachedPrompt / 1_000_000) * pricing.inputCacheMiss +
|
|
20
|
+
(cachedTokens / 1_000_000) * pricing.inputCacheHit;
|
|
21
|
+
const outputCost = (completionTokens / 1_000_000) * pricing.output;
|
|
22
|
+
const totalCost = inputCost + outputCost;
|
|
23
|
+
const formattedCost = totalCost < 0.0001
|
|
24
|
+
? `$${totalCost.toFixed(5)} USD`
|
|
25
|
+
: `$${totalCost.toFixed(4)} USD`;
|
|
26
|
+
return {
|
|
27
|
+
inputCost,
|
|
28
|
+
outputCost,
|
|
29
|
+
totalCost,
|
|
30
|
+
formattedCost,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
getSummary(modelName = "deepseek-v4-flash") {
|
|
34
|
+
const { formattedCost } = this.calculateCost(undefined, undefined, undefined, modelName);
|
|
35
|
+
const totalTokens = this.totalPromptTokens + this.totalCompletionTokens;
|
|
36
|
+
return `Sesión total: ${totalTokens.toLocaleString()} tokens (Caché: ${this.cachedPromptTokens.toLocaleString()}) | Inversión: ${formattedCost}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface FileSnapshot {
|
|
2
|
+
filePath: string;
|
|
3
|
+
previousContent: string | null;
|
|
4
|
+
}
|
|
5
|
+
export interface Checkpoint {
|
|
6
|
+
id: string;
|
|
7
|
+
timestamp: string;
|
|
8
|
+
description: string;
|
|
9
|
+
snapshots: FileSnapshot[];
|
|
10
|
+
}
|
|
11
|
+
export declare class UndoManager {
|
|
12
|
+
private checkpoints;
|
|
13
|
+
private currentTurnSnapshots;
|
|
14
|
+
/**
|
|
15
|
+
* Captures the state of a file before modifying or deleting it
|
|
16
|
+
*/
|
|
17
|
+
snapshotFile(filePath: string, cwd?: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Finalizes the current turn and saves a rollback checkpoint
|
|
20
|
+
*/
|
|
21
|
+
commitTurn(description: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Undoes the last turn of modifications
|
|
24
|
+
*/
|
|
25
|
+
undoLast(): {
|
|
26
|
+
success: boolean;
|
|
27
|
+
message: string;
|
|
28
|
+
restoredFiles: string[];
|
|
29
|
+
};
|
|
30
|
+
getHistory(): {
|
|
31
|
+
id: string;
|
|
32
|
+
description: string;
|
|
33
|
+
time: string;
|
|
34
|
+
fileCount: number;
|
|
35
|
+
}[];
|
|
36
|
+
}
|
|
37
|
+
export declare const globalUndoManager: UndoManager;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export class UndoManager {
|
|
4
|
+
checkpoints = [];
|
|
5
|
+
currentTurnSnapshots = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Captures the state of a file before modifying or deleting it
|
|
8
|
+
*/
|
|
9
|
+
snapshotFile(filePath, cwd = process.cwd()) {
|
|
10
|
+
const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
11
|
+
if (this.currentTurnSnapshots.has(resolved))
|
|
12
|
+
return; // Already captured for this turn
|
|
13
|
+
if (fs.existsSync(resolved)) {
|
|
14
|
+
try {
|
|
15
|
+
const content = fs.readFileSync(resolved, "utf-8");
|
|
16
|
+
this.currentTurnSnapshots.set(resolved, content);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
this.currentTurnSnapshots.set(resolved, null);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
this.currentTurnSnapshots.set(resolved, null); // New file to be created
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Finalizes the current turn and saves a rollback checkpoint
|
|
28
|
+
*/
|
|
29
|
+
commitTurn(description) {
|
|
30
|
+
if (this.currentTurnSnapshots.size === 0)
|
|
31
|
+
return;
|
|
32
|
+
const snapshots = [];
|
|
33
|
+
this.currentTurnSnapshots.forEach((content, filePath) => {
|
|
34
|
+
snapshots.push({ filePath, previousContent: content });
|
|
35
|
+
});
|
|
36
|
+
this.checkpoints.push({
|
|
37
|
+
id: `chk_${Date.now()}`,
|
|
38
|
+
timestamp: new Date().toLocaleTimeString(),
|
|
39
|
+
description,
|
|
40
|
+
snapshots,
|
|
41
|
+
});
|
|
42
|
+
this.currentTurnSnapshots.clear();
|
|
43
|
+
// Keep maximum 10 recent checkpoints in memory
|
|
44
|
+
if (this.checkpoints.length > 10) {
|
|
45
|
+
this.checkpoints.shift();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Undoes the last turn of modifications
|
|
50
|
+
*/
|
|
51
|
+
undoLast() {
|
|
52
|
+
if (this.checkpoints.length === 0) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
message: "No hay acciones previas para deshacer en esta sesión.",
|
|
56
|
+
restoredFiles: [],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const checkpoint = this.checkpoints.pop();
|
|
60
|
+
const restoredFiles = [];
|
|
61
|
+
for (const snap of checkpoint.snapshots) {
|
|
62
|
+
try {
|
|
63
|
+
if (snap.previousContent === null) {
|
|
64
|
+
// File was newly created during the turn, delete it
|
|
65
|
+
if (fs.existsSync(snap.filePath)) {
|
|
66
|
+
fs.unlinkSync(snap.filePath);
|
|
67
|
+
restoredFiles.push(`(Eliminado archivo creado) ${path.basename(snap.filePath)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// File was modified or deleted, restore original content
|
|
72
|
+
fs.writeFileSync(snap.filePath, snap.previousContent, "utf-8");
|
|
73
|
+
restoredFiles.push(`(Restaurado) ${path.basename(snap.filePath)}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
restoredFiles.push(`(Error restaurando) ${path.basename(snap.filePath)}: ${err?.message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
success: true,
|
|
82
|
+
message: `✔ Deshecho con éxito: "${checkpoint.description}" (${checkpoint.timestamp})`,
|
|
83
|
+
restoredFiles,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
getHistory() {
|
|
87
|
+
return this.checkpoints.map((c) => ({
|
|
88
|
+
id: c.id,
|
|
89
|
+
description: c.description,
|
|
90
|
+
time: c.timestamp,
|
|
91
|
+
fileCount: c.snapshots.length,
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export const globalUndoManager = new UndoManager();
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** API de DeepSeek directa. La usa quien trae su propia clave. */
|
|
2
|
+
export declare const DIRECT_ENGINE_URL = "https://api.deepseek.com";
|
|
3
|
+
/**
|
|
4
|
+
* Proxy propio para los suscriptores.
|
|
5
|
+
*
|
|
6
|
+
* En modo suscripcion el cliente NO tiene clave de DeepSeek: manda su clave de
|
|
7
|
+
* licencia y el proxy, que si guarda la clave maestra, reenvia la peticion. Por
|
|
8
|
+
* eso la clave maestra nunca viaja dentro del programa que se reparte.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MANAGED_ENGINE_URL: string;
|
|
11
|
+
/**
|
|
12
|
+
* A donde se habla.
|
|
13
|
+
*
|
|
14
|
+
* Estaba fijo en el codigo, asi que apuntar al proxy exigia recompilar y
|
|
15
|
+
* republicar. Ahora manda, por orden: la variable de entorno (para pruebas), lo
|
|
16
|
+
* guardado en ~/.chocolatitorc, y si no, el destino que corresponda al modo.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getEngineBaseUrl(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Compatibilidad: se conserva el nombre antiguo, pero ya no es una constante
|
|
21
|
+
* congelada al importar. Se resuelve en cada uso.
|
|
22
|
+
*/
|
|
23
|
+
export declare const ENGINE_BASE_URL: string;
|
|
24
|
+
/**
|
|
25
|
+
* Modelo multimodal usado como "ojos" del agente.
|
|
26
|
+
* El motor principal es de texto, asi que las capturas se traducen a texto con
|
|
27
|
+
* este modelo antes de llegar al bucle. Se puede sobrescribir con la variable de
|
|
28
|
+
* entorno CHOCOLATITO_VISION_MODEL.
|
|
29
|
+
*/
|
|
30
|
+
export declare const VISION_MODEL: string;
|
|
31
|
+
/**
|
|
32
|
+
* Ventana de contexto real de los modelos V4: 1.000.000 de tokens.
|
|
33
|
+
*
|
|
34
|
+
* Antes el proyecto asumia 64.000 y compactaba el historial cada 16 mensajes.
|
|
35
|
+
* Con una ventana 15 veces mayor, eso significaba tirar a la basura casi todo el
|
|
36
|
+
* contexto util cada pocos turnos: el agente se olvidaba de lo que acababa de
|
|
37
|
+
* hacer y repetia trabajo. Era el mayor lastre de calidad del proyecto.
|
|
38
|
+
*/
|
|
39
|
+
export declare const CONTEXT_WINDOW = 1000000;
|
|
40
|
+
/** Limite de salida por respuesta. Sin esto el SDK usa un maximo bajo. */
|
|
41
|
+
export declare const MAX_OUTPUT_TOKENS = 16384;
|
|
42
|
+
/**
|
|
43
|
+
* Umbral de auto-compactacion: solo cuando de verdad se acerca el limite.
|
|
44
|
+
* Se deja margen suficiente para una respuesta larga y las herramientas.
|
|
45
|
+
*/
|
|
46
|
+
export declare const COMPACT_THRESHOLD_TOKENS: number;
|
|
47
|
+
export interface EspectroModel {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
description: string;
|
|
51
|
+
underlyingModel: string;
|
|
52
|
+
effortLevel: string;
|
|
53
|
+
/** Temperatura del muestreo para este nivel. */
|
|
54
|
+
temperature: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Los alias antiguos 'deepseek-chat' y 'deepseek-reasoner' resuelven HOY los dos
|
|
58
|
+
* a deepseek-v4-flash. Es decir: los cuatro niveles de esfuerzo corrian
|
|
59
|
+
* exactamente el mismo modelo y el nivel "MAX" no aportaba nada.
|
|
60
|
+
* Ahora los niveles altos usan deepseek-v4-pro, que si es mas capaz.
|
|
61
|
+
*/
|
|
62
|
+
export declare const ESPECTRO_MODELS: Record<string, EspectroModel>;
|
|
63
|
+
export declare const DEFAULT_ESPECTRO: EspectroModel;
|
|
64
|
+
export interface ModelPricing {
|
|
65
|
+
inputCacheMiss: number;
|
|
66
|
+
inputCacheHit: number;
|
|
67
|
+
output: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* USD por millón de tokens, tarifa estándar (fuera del descuento de horario
|
|
71
|
+
* valle). Los valores anteriores estaban tomados de la generación V3 y
|
|
72
|
+
* subestimaban el gasto real entre 3 y 4 veces.
|
|
73
|
+
*/
|
|
74
|
+
export declare const MODEL_PRICING: Record<string, ModelPricing>;
|
|
75
|
+
export declare const PRICING: ModelPricing;
|
|
76
|
+
export declare const CONFIG_FILE_NAME = ".chocolatitorc";
|
|
77
|
+
export declare const RULES_FILE_NAME = "CHOCOLATITO.md";
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/** API de DeepSeek directa. La usa quien trae su propia clave. */
|
|
5
|
+
export const DIRECT_ENGINE_URL = "https://api.deepseek.com";
|
|
6
|
+
/**
|
|
7
|
+
* Proxy propio para los suscriptores.
|
|
8
|
+
*
|
|
9
|
+
* En modo suscripcion el cliente NO tiene clave de DeepSeek: manda su clave de
|
|
10
|
+
* licencia y el proxy, que si guarda la clave maestra, reenvia la peticion. Por
|
|
11
|
+
* eso la clave maestra nunca viaja dentro del programa que se reparte.
|
|
12
|
+
*/
|
|
13
|
+
export const MANAGED_ENGINE_URL = process.env.CHOCOLATITO_PROXY_URL?.trim() ||
|
|
14
|
+
"https://chocolatito-proxy.chocolatito.workers.dev/v1";
|
|
15
|
+
function rc() {
|
|
16
|
+
try {
|
|
17
|
+
const p = path.join(os.homedir(), ".chocolatitorc");
|
|
18
|
+
if (!fs.existsSync(p))
|
|
19
|
+
return {};
|
|
20
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A donde se habla.
|
|
28
|
+
*
|
|
29
|
+
* Estaba fijo en el codigo, asi que apuntar al proxy exigia recompilar y
|
|
30
|
+
* republicar. Ahora manda, por orden: la variable de entorno (para pruebas), lo
|
|
31
|
+
* guardado en ~/.chocolatitorc, y si no, el destino que corresponda al modo.
|
|
32
|
+
*/
|
|
33
|
+
export function getEngineBaseUrl() {
|
|
34
|
+
const env = process.env.CHOCOLATITO_ENGINE_URL?.trim();
|
|
35
|
+
if (env)
|
|
36
|
+
return env;
|
|
37
|
+
const cfg = rc();
|
|
38
|
+
if (typeof cfg.engineUrl === "string" && cfg.engineUrl.trim())
|
|
39
|
+
return cfg.engineUrl.trim();
|
|
40
|
+
return cfg.managed === true ? MANAGED_ENGINE_URL : DIRECT_ENGINE_URL;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Compatibilidad: se conserva el nombre antiguo, pero ya no es una constante
|
|
44
|
+
* congelada al importar. Se resuelve en cada uso.
|
|
45
|
+
*/
|
|
46
|
+
export const ENGINE_BASE_URL = getEngineBaseUrl();
|
|
47
|
+
/**
|
|
48
|
+
* Modelo multimodal usado como "ojos" del agente.
|
|
49
|
+
* El motor principal es de texto, asi que las capturas se traducen a texto con
|
|
50
|
+
* este modelo antes de llegar al bucle. Se puede sobrescribir con la variable de
|
|
51
|
+
* entorno CHOCOLATITO_VISION_MODEL.
|
|
52
|
+
*/
|
|
53
|
+
export const VISION_MODEL = process.env.CHOCOLATITO_VISION_MODEL?.trim() || "deepseek-v4-flash-vision-exp";
|
|
54
|
+
/**
|
|
55
|
+
* Ventana de contexto real de los modelos V4: 1.000.000 de tokens.
|
|
56
|
+
*
|
|
57
|
+
* Antes el proyecto asumia 64.000 y compactaba el historial cada 16 mensajes.
|
|
58
|
+
* Con una ventana 15 veces mayor, eso significaba tirar a la basura casi todo el
|
|
59
|
+
* contexto util cada pocos turnos: el agente se olvidaba de lo que acababa de
|
|
60
|
+
* hacer y repetia trabajo. Era el mayor lastre de calidad del proyecto.
|
|
61
|
+
*/
|
|
62
|
+
export const CONTEXT_WINDOW = 1_000_000;
|
|
63
|
+
/** Limite de salida por respuesta. Sin esto el SDK usa un maximo bajo. */
|
|
64
|
+
export const MAX_OUTPUT_TOKENS = 16_384;
|
|
65
|
+
/**
|
|
66
|
+
* Umbral de auto-compactacion: solo cuando de verdad se acerca el limite.
|
|
67
|
+
* Se deja margen suficiente para una respuesta larga y las herramientas.
|
|
68
|
+
*/
|
|
69
|
+
export const COMPACT_THRESHOLD_TOKENS = Math.floor(CONTEXT_WINDOW * 0.75);
|
|
70
|
+
/**
|
|
71
|
+
* Los alias antiguos 'deepseek-chat' y 'deepseek-reasoner' resuelven HOY los dos
|
|
72
|
+
* a deepseek-v4-flash. Es decir: los cuatro niveles de esfuerzo corrian
|
|
73
|
+
* exactamente el mismo modelo y el nivel "MAX" no aportaba nada.
|
|
74
|
+
* Ahora los niveles altos usan deepseek-v4-pro, que si es mas capaz.
|
|
75
|
+
*/
|
|
76
|
+
export const ESPECTRO_MODELS = {
|
|
77
|
+
"1.0": {
|
|
78
|
+
id: "1.0",
|
|
79
|
+
name: "Espectro 1.0 (Ágil)",
|
|
80
|
+
description: "Ultra rápido, ideal para tareas cotidianas y código directo",
|
|
81
|
+
underlyingModel: "deepseek-v4-flash",
|
|
82
|
+
effortLevel: "Normal",
|
|
83
|
+
temperature: 0.1,
|
|
84
|
+
},
|
|
85
|
+
"1.5": {
|
|
86
|
+
id: "1.5",
|
|
87
|
+
name: "Espectro 1.5 (Avanzado)",
|
|
88
|
+
description: "Alta precisión con balance de velocidad para refactorización",
|
|
89
|
+
underlyingModel: "deepseek-v4-flash",
|
|
90
|
+
effortLevel: "Alto",
|
|
91
|
+
temperature: 0.3,
|
|
92
|
+
},
|
|
93
|
+
"2.0": {
|
|
94
|
+
id: "2.0",
|
|
95
|
+
name: "Espectro 2.0 (Razonamiento Pro)",
|
|
96
|
+
description: "Modelo Pro para lógica compleja, bugs difíciles y arquitectura",
|
|
97
|
+
underlyingModel: "deepseek-v4-pro",
|
|
98
|
+
effortLevel: "Extendido",
|
|
99
|
+
temperature: 0.1,
|
|
100
|
+
},
|
|
101
|
+
"2.5": {
|
|
102
|
+
id: "2.5",
|
|
103
|
+
name: "Espectro 2.5 MAX (Máxima Potencia)",
|
|
104
|
+
description: "Modelo Pro con exploración amplia para proyectos enteros",
|
|
105
|
+
underlyingModel: "deepseek-v4-pro",
|
|
106
|
+
effortLevel: "Máximo",
|
|
107
|
+
temperature: 0.35,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
export const DEFAULT_ESPECTRO = ESPECTRO_MODELS["1.0"];
|
|
111
|
+
/**
|
|
112
|
+
* USD por millón de tokens, tarifa estándar (fuera del descuento de horario
|
|
113
|
+
* valle). Los valores anteriores estaban tomados de la generación V3 y
|
|
114
|
+
* subestimaban el gasto real entre 3 y 4 veces.
|
|
115
|
+
*/
|
|
116
|
+
export const MODEL_PRICING = {
|
|
117
|
+
"deepseek-v4-flash": {
|
|
118
|
+
inputCacheMiss: 0.44,
|
|
119
|
+
inputCacheHit: 0.014,
|
|
120
|
+
output: 1.32,
|
|
121
|
+
},
|
|
122
|
+
"deepseek-v4-pro": {
|
|
123
|
+
inputCacheMiss: 1.32,
|
|
124
|
+
inputCacheHit: 0.044,
|
|
125
|
+
output: 3.96,
|
|
126
|
+
},
|
|
127
|
+
"deepseek-v4-flash-vision-exp": {
|
|
128
|
+
inputCacheMiss: 0.44,
|
|
129
|
+
inputCacheHit: 0.014,
|
|
130
|
+
output: 1.32,
|
|
131
|
+
},
|
|
132
|
+
// Alias heredados, por si alguna sesión guardada los referencia.
|
|
133
|
+
"deepseek-chat": {
|
|
134
|
+
inputCacheMiss: 0.44,
|
|
135
|
+
inputCacheHit: 0.014,
|
|
136
|
+
output: 1.32,
|
|
137
|
+
},
|
|
138
|
+
"deepseek-reasoner": {
|
|
139
|
+
inputCacheMiss: 0.44,
|
|
140
|
+
inputCacheHit: 0.014,
|
|
141
|
+
output: 1.32,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
export const PRICING = MODEL_PRICING["deepseek-v4-flash"];
|
|
145
|
+
export const CONFIG_FILE_NAME = ".chocolatitorc";
|
|
146
|
+
export const RULES_FILE_NAME = "CHOCOLATITO.md";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
/**
|
|
3
|
+
* QUIEN HABLA CON EL MOTOR Y COMO SE IDENTIFICA
|
|
4
|
+
*
|
|
5
|
+
* Habia tres sitios construyendo su propio cliente de OpenAI con la clave y la
|
|
6
|
+
* URL, sin nada mas. Eso dejaba tres agujeros para el modelo de suscripcion:
|
|
7
|
+
*
|
|
8
|
+
* 1. La licencia no viajaba. El CLI mandaba `Authorization: Bearer <clave de
|
|
9
|
+
* DeepSeek>` y punto; la licencia se quedaba en el archivo de configuracion.
|
|
10
|
+
* Un proxy que la esperase habria respondido 401 a todos los suscriptores.
|
|
11
|
+
*
|
|
12
|
+
* 2. El proxy no podia distinguir de que ruta venia el gasto. El bucle, los
|
|
13
|
+
* subagentes, la vision y la compactacion llegaban con cuerpos casi
|
|
14
|
+
* indistinguibles, asi que era imposible decirle al usuario en que se le fue
|
|
15
|
+
* la cuota, ni limitar una ruta concreta.
|
|
16
|
+
*
|
|
17
|
+
* 3. Nada impedia usar el proxy como API generica barata. Es un endpoint
|
|
18
|
+
* compatible con OpenAI: sin una marca propia obligatoria, apuntar ahi
|
|
19
|
+
* cualquier otro cliente da el mismo servicio por el mismo precio.
|
|
20
|
+
*/
|
|
21
|
+
export type EngineRoute = "loop" | "subagent" | "vision" | "compact";
|
|
22
|
+
/** Version del paquete instalado, para versionar el contrato con el proxy. */
|
|
23
|
+
export declare function clientVersion(): string;
|
|
24
|
+
/**
|
|
25
|
+
* La credencial que se manda en el Bearer.
|
|
26
|
+
*
|
|
27
|
+
* En suscripcion es la clave de licencia: el cliente no tiene clave de DeepSeek
|
|
28
|
+
* y no debe tenerla, porque la maestra vive en el proxy. Con clave propia se
|
|
29
|
+
* manda la del usuario, como siempre.
|
|
30
|
+
*/
|
|
31
|
+
export declare function engineCredential(fallbackApiKey: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Cabeceras propias en cada peticion.
|
|
34
|
+
*
|
|
35
|
+
* Van en TODAS las rutas para que el proxy pueda atribuir el gasto, rechazar
|
|
36
|
+
* clientes de version incompatible y distinguir a Chocolatito Code de un SDK
|
|
37
|
+
* cualquiera apuntando al mismo endpoint.
|
|
38
|
+
*/
|
|
39
|
+
export declare function engineHeaders(route: EngineRoute): Record<string, string>;
|
|
40
|
+
/** Cliente del motor ya configurado. Es el unico sitio donde se construye. */
|
|
41
|
+
export declare function createEngineClient(apiKey: string, route: EngineRoute): OpenAI;
|
|
42
|
+
/**
|
|
43
|
+
* Traduce un error del motor a algo que el usuario entienda.
|
|
44
|
+
*
|
|
45
|
+
* Sin esto, el proxy diciendo "se te acabo la cuota" (402) llega a pantalla como
|
|
46
|
+
* "Error en el motor tras varios reintentos: 402 ...", que ademas miente: un 402
|
|
47
|
+
* no se reintenta nunca.
|
|
48
|
+
*/
|
|
49
|
+
export declare function explicarErrorDelMotor(err: any): string | null;
|