chocolatito-code 1.0.0 → 1.1.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/README.md +89 -0
- package/dist/agent/compaction.d.ts +65 -0
- package/dist/agent/compaction.js +134 -0
- package/dist/agent/context.d.ts +3 -0
- package/dist/agent/context.js +35 -0
- package/dist/agent/loop.d.ts +91 -2
- package/dist/agent/loop.js +536 -78
- package/dist/agent/loopDetector.d.ts +57 -0
- package/dist/agent/loopDetector.js +0 -0
- package/dist/agent/mentions.d.ts +13 -0
- package/dist/agent/mentions.js +49 -0
- package/dist/agent/repoMap.d.ts +117 -0
- package/dist/agent/repoMap.js +560 -0
- package/dist/agent/result.d.ts +64 -0
- package/dist/agent/result.js +48 -0
- package/dist/agent/retry.d.ts +35 -0
- package/dist/agent/retry.js +123 -4
- package/dist/agent/subagent.js +62 -15
- package/dist/agent/syntaxValidator.js +288 -52
- package/dist/agent/toolGate.d.ts +58 -0
- package/dist/agent/toolGate.js +111 -0
- package/dist/agent/tracker.js +4 -7
- package/dist/agent/undoManager.d.ts +44 -11
- package/dist/agent/undoManager.js +95 -24
- package/dist/agent/usageMeter.d.ts +56 -0
- package/dist/agent/usageMeter.js +51 -0
- package/dist/agent/verifier.d.ts +49 -0
- package/dist/agent/verifier.js +158 -0
- package/dist/config/constants.js +14 -0
- package/dist/config/engine.d.ts +19 -0
- package/dist/config/engine.js +35 -3
- package/dist/config/license.d.ts +5 -0
- package/dist/config/license.js +107 -9
- package/dist/config/limits.d.ts +28 -0
- package/dist/config/limits.js +51 -0
- package/dist/config/permissions.d.ts +76 -1
- package/dist/config/permissions.js +379 -23
- package/dist/config/quota.js +24 -1
- package/dist/config/updater.d.ts +68 -0
- package/dist/config/updater.js +215 -0
- package/dist/hooks/manager.js +159 -14
- package/dist/index.js +222 -34
- package/dist/mcp/client.d.ts +22 -0
- package/dist/mcp/client.js +93 -11
- package/dist/prompts/systemPrompt.js +12 -1
- package/dist/sessions/manager.d.ts +12 -0
- package/dist/sessions/manager.js +33 -5
- package/dist/sessions/resume.d.ts +36 -0
- package/dist/sessions/resume.js +43 -0
- package/dist/skills/manager.d.ts +1 -0
- package/dist/skills/manager.js +9 -0
- package/dist/tools/binary.d.ts +46 -0
- package/dist/tools/binary.js +100 -0
- package/dist/tools/browserCdp.js +13 -1
- package/dist/tools/browserExtension.d.ts +5 -0
- package/dist/tools/browserExtension.js +202 -18
- package/dist/tools/browserSession.d.ts +23 -0
- package/dist/tools/browserSession.js +28 -0
- package/dist/tools/computerUse.js +33 -1
- package/dist/tools/createImage.d.ts +11 -0
- package/dist/tools/createImage.js +13 -1
- package/dist/tools/definitions.js +9 -3
- package/dist/tools/editDiagnosis.d.ts +54 -0
- package/dist/tools/editDiagnosis.js +142 -0
- package/dist/tools/editFile.d.ts +1 -0
- package/dist/tools/editFile.js +118 -62
- package/dist/tools/findFiles.d.ts +21 -0
- package/dist/tools/findFiles.js +75 -6
- package/dist/tools/getSystemInfo.js +15 -7
- package/dist/tools/gitTools.d.ts +24 -0
- package/dist/tools/gitTools.js +87 -26
- package/dist/tools/moveCopyFile.d.ts +18 -1
- package/dist/tools/moveCopyFile.js +74 -4
- package/dist/tools/patchCascade.d.ts +57 -0
- package/dist/tools/patchCascade.js +338 -0
- package/dist/tools/runCommand.js +213 -25
- package/dist/tools/runner.js +57 -4
- package/dist/tools/toolDefsComputer.js +12 -2
- package/dist/tools/truncator.d.ts +27 -1
- package/dist/tools/truncator.js +117 -11
- package/dist/tools/viewFile.js +15 -11
- package/dist/tools/visionBridge.js +7 -0
- package/dist/tools/webSearch.d.ts +21 -0
- package/dist/tools/webSearch.js +125 -14
- package/dist/tools/writeFile.js +18 -1
- package/dist/ui/interrupt.d.ts +4 -1
- package/dist/ui/interrupt.js +56 -5
- package/dist/ui/keyboardGuard.d.ts +55 -0
- package/dist/ui/keyboardGuard.js +65 -0
- package/dist/ui/loopPrompt.d.ts +17 -0
- package/dist/ui/loopPrompt.js +59 -0
- package/dist/ui/permissionPrompt.js +84 -2
- package/dist/ui/prompt.d.ts +35 -0
- package/dist/ui/prompt.js +345 -125
- package/dist/ui/reasoningStream.d.ts +8 -0
- package/dist/ui/reasoningStream.js +99 -7
- package/extension/README.md +95 -74
- package/extension/background.js +598 -178
- package/extension/content.js +92 -9
- package/extension/manifest.json +2 -1
- package/extension/popup.js +28 -12
- package/package.json +1 -1
package/dist/agent/retry.d.ts
CHANGED
|
@@ -5,6 +5,19 @@
|
|
|
5
5
|
* conexion cortada) abortaba el turno entero con "Error en el motor" y el
|
|
6
6
|
* usuario perdia el trabajo en curso. Un fallo transitorio no deberia costar
|
|
7
7
|
* una tarea.
|
|
8
|
+
*
|
|
9
|
+
* Dos reglas que se anadieron despues, y que cambian el resultado mas que el
|
|
10
|
+
* propio backoff:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Si el servidor dice cuanto esperar, se le hace caso.** Ante un 429 con
|
|
13
|
+
* `retry-after: 60`, esperar 0,8 s y volver a chocar gasta los cuatro
|
|
14
|
+
* intentos en seis segundos y acaba en el mismo error, pero ahora sin
|
|
15
|
+
* intentos. El tope de espera solo tiene sentido cuando nadie ha dicho
|
|
16
|
+
* nada; cuando lo dice el servidor, el tope sobra.
|
|
17
|
+
*
|
|
18
|
+
* 2. **Un desbordamiento de contexto no se reintenta nunca.** Si la peticion
|
|
19
|
+
* no cabe, mandarla otra vez identica no puede funcionar: hay que compactar
|
|
20
|
+
* o abortar. Reintentarla son tres esperas tiradas y tres facturas.
|
|
8
21
|
*/
|
|
9
22
|
export interface RetryOptions {
|
|
10
23
|
attempts?: number;
|
|
@@ -12,6 +25,28 @@ export interface RetryOptions {
|
|
|
12
25
|
maxDelayMs?: number;
|
|
13
26
|
onRetry?: (attempt: number, delayMs: number, reason: string) => void;
|
|
14
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Espera que ha pedido el servidor, en milisegundos, o null si no ha pedido
|
|
30
|
+
* ninguna. Se mira primero `retry-after-ms` porque es exacta; `retry-after`
|
|
31
|
+
* puede venir en segundos o como fecha HTTP.
|
|
32
|
+
*/
|
|
33
|
+
export declare function esperaPedidaPorElServidor(err: any): number | null;
|
|
34
|
+
/**
|
|
35
|
+
* El contexto no cabe. Puede llegar como 400 (que ya no se reintentaba, por
|
|
36
|
+
* suerte mas que por diseno) o disfrazado de 5xx segun el proveedor, que es el
|
|
37
|
+
* caso que hacia perder los cuatro intentos.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isContextOverflow(err: any): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* El 429 de "tienes N peticiones en curso" NO es un limite de ritmo del motor.
|
|
42
|
+
*
|
|
43
|
+
* Es que los huecos de esta licencia estan ocupados, y si estan atascados por
|
|
44
|
+
* una peticion que se perdio, no se desatascan en seis segundos: se desatascan
|
|
45
|
+
* cuando caduca la reserva. Reintentarlo cuatro veces con espera exponencial
|
|
46
|
+
* gasta los intentos, tarda seis segundos y acaba en "Error en el motor tras
|
|
47
|
+
* varios reintentos", que no dice nada de lo que de verdad pasa.
|
|
48
|
+
*/
|
|
49
|
+
export declare function esDemasiadasEnCurso(err: any): boolean;
|
|
15
50
|
/** Errores que merece la pena reintentar: transitorios, no de programacion. */
|
|
16
51
|
export declare function isRetryable(err: any): boolean;
|
|
17
52
|
export declare function withRetry<T>(fn: () => Promise<T>, opts?: RetryOptions): Promise<T>;
|
package/dist/agent/retry.js
CHANGED
|
@@ -5,9 +5,118 @@
|
|
|
5
5
|
* conexion cortada) abortaba el turno entero con "Error en el motor" y el
|
|
6
6
|
* usuario perdia el trabajo en curso. Un fallo transitorio no deberia costar
|
|
7
7
|
* una tarea.
|
|
8
|
+
*
|
|
9
|
+
* Dos reglas que se anadieron despues, y que cambian el resultado mas que el
|
|
10
|
+
* propio backoff:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Si el servidor dice cuanto esperar, se le hace caso.** Ante un 429 con
|
|
13
|
+
* `retry-after: 60`, esperar 0,8 s y volver a chocar gasta los cuatro
|
|
14
|
+
* intentos en seis segundos y acaba en el mismo error, pero ahora sin
|
|
15
|
+
* intentos. El tope de espera solo tiene sentido cuando nadie ha dicho
|
|
16
|
+
* nada; cuando lo dice el servidor, el tope sobra.
|
|
17
|
+
*
|
|
18
|
+
* 2. **Un desbordamiento de contexto no se reintenta nunca.** Si la peticion
|
|
19
|
+
* no cabe, mandarla otra vez identica no puede funcionar: hay que compactar
|
|
20
|
+
* o abortar. Reintentarla son tres esperas tiradas y tres facturas.
|
|
21
|
+
*/
|
|
22
|
+
/** Lee una cabecera venga como objeto plano o como Headers de fetch. */
|
|
23
|
+
function leerCabecera(err, nombre) {
|
|
24
|
+
const headers = err?.headers ?? err?.response?.headers;
|
|
25
|
+
if (!headers)
|
|
26
|
+
return null;
|
|
27
|
+
if (typeof headers.get === "function") {
|
|
28
|
+
const v = headers.get(nombre);
|
|
29
|
+
return v == null ? null : String(v);
|
|
30
|
+
}
|
|
31
|
+
// Los objetos planos pueden traer la clave con cualquier caja.
|
|
32
|
+
for (const clave of Object.keys(headers)) {
|
|
33
|
+
if (clave.toLowerCase() === nombre) {
|
|
34
|
+
const v = headers[clave];
|
|
35
|
+
return v == null ? null : String(v);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Espera que ha pedido el servidor, en milisegundos, o null si no ha pedido
|
|
42
|
+
* ninguna. Se mira primero `retry-after-ms` porque es exacta; `retry-after`
|
|
43
|
+
* puede venir en segundos o como fecha HTTP.
|
|
44
|
+
*/
|
|
45
|
+
export function esperaPedidaPorElServidor(err) {
|
|
46
|
+
const ms = leerCabecera(err, "retry-after-ms");
|
|
47
|
+
if (ms !== null) {
|
|
48
|
+
const n = Number(ms);
|
|
49
|
+
if (Number.isFinite(n) && n >= 0)
|
|
50
|
+
return n;
|
|
51
|
+
}
|
|
52
|
+
const after = leerCabecera(err, "retry-after");
|
|
53
|
+
if (after === null)
|
|
54
|
+
return null;
|
|
55
|
+
const segundos = Number(after);
|
|
56
|
+
if (Number.isFinite(segundos) && segundos >= 0)
|
|
57
|
+
return segundos * 1000;
|
|
58
|
+
const fecha = Date.parse(after);
|
|
59
|
+
if (!Number.isNaN(fecha)) {
|
|
60
|
+
// Una fecha ya pasada significa "ya puedes": cero, no un numero negativo.
|
|
61
|
+
return Math.max(0, fecha - Date.now());
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* El contexto no cabe. Puede llegar como 400 (que ya no se reintentaba, por
|
|
67
|
+
* suerte mas que por diseno) o disfrazado de 5xx segun el proveedor, que es el
|
|
68
|
+
* caso que hacia perder los cuatro intentos.
|
|
69
|
+
*/
|
|
70
|
+
export function isContextOverflow(err) {
|
|
71
|
+
const texto = [
|
|
72
|
+
err?.message,
|
|
73
|
+
err?.error?.message,
|
|
74
|
+
err?.response?.data?.error?.message,
|
|
75
|
+
err?.code,
|
|
76
|
+
err?.error?.code,
|
|
77
|
+
]
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.join(" ")
|
|
80
|
+
.toLowerCase();
|
|
81
|
+
if (!texto)
|
|
82
|
+
return false;
|
|
83
|
+
return (texto.includes("context_length_exceeded") ||
|
|
84
|
+
texto.includes("context length") ||
|
|
85
|
+
texto.includes("maximum context") ||
|
|
86
|
+
texto.includes("context window") ||
|
|
87
|
+
texto.includes("too many tokens") ||
|
|
88
|
+
texto.includes("reduce the length of the messages") ||
|
|
89
|
+
(texto.includes("token") && texto.includes("exceed")));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* El 429 de "tienes N peticiones en curso" NO es un limite de ritmo del motor.
|
|
93
|
+
*
|
|
94
|
+
* Es que los huecos de esta licencia estan ocupados, y si estan atascados por
|
|
95
|
+
* una peticion que se perdio, no se desatascan en seis segundos: se desatascan
|
|
96
|
+
* cuando caduca la reserva. Reintentarlo cuatro veces con espera exponencial
|
|
97
|
+
* gasta los intentos, tarda seis segundos y acaba en "Error en el motor tras
|
|
98
|
+
* varios reintentos", que no dice nada de lo que de verdad pasa.
|
|
8
99
|
*/
|
|
100
|
+
export function esDemasiadasEnCurso(err) {
|
|
101
|
+
if ((err?.status ?? err?.response?.status) !== 429)
|
|
102
|
+
return false;
|
|
103
|
+
const codigo = String(err?.error?.code ?? err?.error?.type ?? "");
|
|
104
|
+
if (codigo === "demasiadas_en_curso")
|
|
105
|
+
return true;
|
|
106
|
+
// Compatibilidad con proxys anteriores al codigo: se mira el texto.
|
|
107
|
+
return /peticiones en curso/i.test(String(err?.error?.message ?? err?.message ?? ""));
|
|
108
|
+
}
|
|
9
109
|
/** Errores que merece la pena reintentar: transitorios, no de programacion. */
|
|
10
110
|
export function isRetryable(err) {
|
|
111
|
+
// Va primero: un desbordamiento de contexto devuelto como 503 seguiria
|
|
112
|
+
// pareciendo transitorio, y no lo es. Repetir la misma peticion que no cabe
|
|
113
|
+
// no puede empezar a caber.
|
|
114
|
+
if (isContextOverflow(err))
|
|
115
|
+
return false;
|
|
116
|
+
// Tampoco esto: no se arregla esperando un poco mas, y hay algo util que
|
|
117
|
+
// decirle al usuario en vez de agotar los intentos en silencio.
|
|
118
|
+
if (esDemasiadasEnCurso(err))
|
|
119
|
+
return false;
|
|
11
120
|
const status = err?.status ?? err?.response?.status;
|
|
12
121
|
if (typeof status === "number") {
|
|
13
122
|
// 429 limite de ritmo; 5xx problemas del servidor. Un 400/401/403 es culpa
|
|
@@ -23,7 +132,12 @@ export function isRetryable(err) {
|
|
|
23
132
|
msg.includes("socket hang up") ||
|
|
24
133
|
msg.includes("network") ||
|
|
25
134
|
msg.includes("fetch failed") ||
|
|
26
|
-
msg.includes("terminated")
|
|
135
|
+
msg.includes("terminated") ||
|
|
136
|
+
// Frases que los proveedores devuelven sin codigo de estado util.
|
|
137
|
+
msg.includes("rate limit") ||
|
|
138
|
+
msg.includes("overloaded") ||
|
|
139
|
+
msg.includes("try again later") ||
|
|
140
|
+
msg.includes("temporarily"));
|
|
27
141
|
}
|
|
28
142
|
function describe(err) {
|
|
29
143
|
const status = err?.status ?? err?.response?.status;
|
|
@@ -47,9 +161,14 @@ export async function withRetry(fn, opts = {}) {
|
|
|
47
161
|
const isLast = i === attempts - 1;
|
|
48
162
|
if (isLast || !isRetryable(err))
|
|
49
163
|
throw err;
|
|
50
|
-
|
|
51
|
-
const delay =
|
|
52
|
-
|
|
164
|
+
const pedida = esperaPedidaPorElServidor(err);
|
|
165
|
+
const delay = pedida !== null
|
|
166
|
+
? // El tope NO se aplica: si el servidor pide 60 s, esperar 15 y
|
|
167
|
+
// volver a chocar es gastar un intento a cambio de nada.
|
|
168
|
+
pedida
|
|
169
|
+
: Math.min(max, base * Math.pow(2, i)) * (0.7 + Math.random() * 0.6);
|
|
170
|
+
const motivo = pedida !== null ? `${describe(err)} (pide esperar)` : describe(err);
|
|
171
|
+
opts.onRetry?.(i + 1, Math.round(delay), motivo);
|
|
53
172
|
await new Promise((r) => setTimeout(r, delay));
|
|
54
173
|
}
|
|
55
174
|
}
|
package/dist/agent/subagent.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { TOOLS, getAllTools } from "../tools/definitions.js";
|
|
2
2
|
import { executeToolCall } from "../tools/runner.js";
|
|
3
3
|
import { truncateOutput } from "../tools/truncator.js";
|
|
4
|
-
import { isReadOnly } from "../tools/safety.js";
|
|
4
|
+
import { isReadOnly, canRunInParallel } from "../tools/safety.js";
|
|
5
5
|
import { MAX_OUTPUT_TOKENS } from "../config/constants.js";
|
|
6
6
|
import { withRetry } from "./retry.js";
|
|
7
7
|
import { createEngineClient } from "../config/engine.js";
|
|
8
|
+
import { autorizarHerramienta, notificarPostHerramienta } from "./toolGate.js";
|
|
9
|
+
import { apuntarUso } from "./usageMeter.js";
|
|
10
|
+
import { leerLimites } from "../config/limits.js";
|
|
8
11
|
/**
|
|
9
12
|
* Subagentes.
|
|
10
13
|
*
|
|
11
|
-
*
|
|
14
|
+
* Arreglos sobre la version anterior:
|
|
12
15
|
*
|
|
13
16
|
* 1. 'explore' y 'plan' recibian TODAS las herramientas, incluidas delete_file,
|
|
14
17
|
* write_file y run_command. Un agente que se presenta como "de solo lectura"
|
|
@@ -21,14 +24,32 @@ import { createEngineClient } from "../config/engine.js";
|
|
|
21
24
|
* resumen de lo averiguado antes de rendirse.
|
|
22
25
|
*
|
|
23
26
|
* 3. Sin reintentos: un 429 mataba al subagente y con el la tarea del padre.
|
|
27
|
+
*
|
|
28
|
+
* 4. Ejecutaba las herramientas a pelo, sin permisos, sin hooks y sin el
|
|
29
|
+
* bloqueo del modo plan. La contencion de 'explore' y 'plan' era solo la
|
|
30
|
+
* lista de herramientas del punto 1, y el tipo 'general' no tenia ninguna:
|
|
31
|
+
* por esa puerta se borraban archivos sin preguntar y se ejecutaban comandos
|
|
32
|
+
* en modo plan, que el producto anuncia como de solo lectura. Ahora cada
|
|
33
|
+
* llamada pasa por la misma puerta que las del bucle padre (toolGate.ts), y
|
|
34
|
+
* la contencion deja de depender de que nadie meta una herramienta de
|
|
35
|
+
* escritura en la lista de lectura.
|
|
36
|
+
*
|
|
37
|
+
* 5. Lanzaba el lote entero con Promise.all "porque un explorador solo lee".
|
|
38
|
+
* Cierto para 'explore' y 'plan', falso para 'general': con write_file y
|
|
39
|
+
* run_command en la mano, dos escrituras sobre el mismo archivo salian a la
|
|
40
|
+
* vez y en orden indeterminado. Se aplica la misma regla que el padre.
|
|
24
41
|
*/
|
|
25
42
|
const READ_ONLY_TYPES = ["explore", "plan"];
|
|
26
43
|
function toolsFor(type) {
|
|
27
44
|
// Un subagente general hereda tambien las herramientas MCP; uno de solo
|
|
28
45
|
// lectura no, porque no se puede saber si una herramienta MCP escribe.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
const base = READ_ONLY_TYPES.includes(type)
|
|
47
|
+
? TOOLS.filter((t) => isReadOnly(t.function.name))
|
|
48
|
+
: getAllTools();
|
|
49
|
+
// Ninguno recibe spawn_agent. Un subagente que puede lanzar subagentes abre un
|
|
50
|
+
// arbol sin fondo: el usuario ve una sola linea "spawn_agent" y detras corren
|
|
51
|
+
// los que sea, gastando cuota que nadie ha contado.
|
|
52
|
+
return base.filter((t) => t.function?.name !== "spawn_agent");
|
|
32
53
|
}
|
|
33
54
|
export async function runSubagent(type, prompt, apiKey, baseURL, modelName, cwd = process.cwd()) {
|
|
34
55
|
const client = createEngineClient(apiKey, "subagent");
|
|
@@ -49,7 +70,24 @@ export async function runSubagent(type, prompt, apiKey, baseURL, modelName, cwd
|
|
|
49
70
|
{ role: "user", content: prompt },
|
|
50
71
|
];
|
|
51
72
|
const tools = toolsFor(type);
|
|
52
|
-
const maxIterations =
|
|
73
|
+
const maxIterations = leerLimites(cwd).iteracionesDeSubagente;
|
|
74
|
+
const etiqueta = `El subagente ${type}`;
|
|
75
|
+
/** Una llamada, con los mismos controles que aplica el bucle padre. */
|
|
76
|
+
async function ejecutarLlamada(tc) {
|
|
77
|
+
let args = {};
|
|
78
|
+
try {
|
|
79
|
+
args = JSON.parse(tc.function.arguments);
|
|
80
|
+
}
|
|
81
|
+
catch { }
|
|
82
|
+
const decision = await autorizarHerramienta(tc.function.name, args, cwd, etiqueta);
|
|
83
|
+
if (!decision.permitida) {
|
|
84
|
+
return { id: tc.id, content: decision.motivo || "Accion no autorizada." };
|
|
85
|
+
}
|
|
86
|
+
const { result } = await executeToolCall(tc.function.name, decision.args, cwd, apiKey, modelName);
|
|
87
|
+
const aviso = await notificarPostHerramienta(tc.function.name, decision.args, result, cwd);
|
|
88
|
+
const texto = aviso ? `${result}\n\n[Hook PostToolUse]: ${aviso}` : result;
|
|
89
|
+
return { id: tc.id, content: truncateOutput(texto, 20_000) };
|
|
90
|
+
}
|
|
53
91
|
for (let iterations = 0; iterations < maxIterations; iterations++) {
|
|
54
92
|
// Al acercarse al limite se avisa, para que cierre en vez de que le corten.
|
|
55
93
|
if (iterations === maxIterations - 2) {
|
|
@@ -72,21 +110,29 @@ export async function runSubagent(type, prompt, apiKey, baseURL, modelName, cwd
|
|
|
72
110
|
catch (err) {
|
|
73
111
|
return `Error en subagente (${type}): ${err?.message || String(err)}`;
|
|
74
112
|
}
|
|
113
|
+
// Un subagente puede encadenar 14 iteraciones con el contexto entero de la
|
|
114
|
+
// subtarea, y todo ese gasto se apuntaba como 0,00 $: la cuota local solo
|
|
115
|
+
// miraba el stream del bucle padre. Se cuenta donde se gasta.
|
|
116
|
+
apuntarUso(res.usage, modelName, "subagent");
|
|
75
117
|
const message = res.choices[0]?.message;
|
|
76
118
|
if (!message)
|
|
77
119
|
break;
|
|
78
120
|
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
79
121
|
messages.push(message);
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
122
|
+
// Un lote va a la vez solo si TODAS las llamadas son de lectura. Aqui se
|
|
123
|
+
// daba por hecho que un subagente solo lee, y para 'general' es falso: dos
|
|
124
|
+
// write_file simultaneos sobre el mismo archivo dejan el resultado al azar,
|
|
125
|
+
// que es justo lo que safety.ts existe para evitar.
|
|
126
|
+
const nombres = message.tool_calls.map((tc) => tc.function.name);
|
|
127
|
+
const results = [];
|
|
128
|
+
if (canRunInParallel(nombres)) {
|
|
129
|
+
results.push(...(await Promise.all(message.tool_calls.map(ejecutarLlamada))));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
for (const tc of message.tool_calls) {
|
|
133
|
+
results.push(await ejecutarLlamada(tc));
|
|
85
134
|
}
|
|
86
|
-
|
|
87
|
-
const { result } = await executeToolCall(tc.function.name, args, cwd, apiKey, modelName);
|
|
88
|
-
return { id: tc.id, content: truncateOutput(result, 20_000) };
|
|
89
|
-
}));
|
|
135
|
+
}
|
|
90
136
|
for (const r of results) {
|
|
91
137
|
messages.push({ role: "tool", tool_call_id: r.id, content: r.content });
|
|
92
138
|
}
|
|
@@ -107,6 +153,7 @@ export async function runSubagent(type, prompt, apiKey, baseURL, modelName, cwd
|
|
|
107
153
|
temperature: 0.1,
|
|
108
154
|
max_tokens: MAX_OUTPUT_TOKENS,
|
|
109
155
|
}));
|
|
156
|
+
apuntarUso(final.usage, modelName, "subagent");
|
|
110
157
|
const text = final.choices[0]?.message?.content;
|
|
111
158
|
if (text)
|
|
112
159
|
return `[Subagente ${type}: limite de iteraciones alcanzado]\n\n${text}`;
|