zerochat 7.3.0__py3-none-any.whl

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.
Files changed (130) hide show
  1. zerochat-7.3.0.dist-info/METADATA +70 -0
  2. zerochat-7.3.0.dist-info/RECORD +130 -0
  3. zerochat-7.3.0.dist-info/WHEEL +5 -0
  4. zerochat-7.3.0.dist-info/entry_points.txt +2 -0
  5. zerochat-7.3.0.dist-info/licenses/LICENSE +34 -0
  6. zerochat-7.3.0.dist-info/top_level.txt +2 -0
  7. zerochat.py +2004 -0
  8. zerochat_runtime/__init__.py +1 -0
  9. zerochat_runtime/assets/css/base.css +199 -0
  10. zerochat_runtime/assets/css/components/composer.css +1258 -0
  11. zerochat_runtime/assets/css/components/debug.css +777 -0
  12. zerochat_runtime/assets/css/components/header.css +97 -0
  13. zerochat_runtime/assets/css/components/icons.css +39 -0
  14. zerochat_runtime/assets/css/components/markdown.css +685 -0
  15. zerochat_runtime/assets/css/components/messages.css +473 -0
  16. zerochat_runtime/assets/css/components/modals.css +1319 -0
  17. zerochat_runtime/assets/css/components/sidebar.css +622 -0
  18. zerochat_runtime/assets/css/components/tools.css +1223 -0
  19. zerochat_runtime/assets/css/layout.css +171 -0
  20. zerochat_runtime/assets/css/print.css +120 -0
  21. zerochat_runtime/assets/css/styles.css +18 -0
  22. zerochat_runtime/assets/css/theme-overrides.css +555 -0
  23. zerochat_runtime/assets/css/tokens.css +165 -0
  24. zerochat_runtime/assets/help/architecture.html +247 -0
  25. zerochat_runtime/assets/help/composer.html +134 -0
  26. zerochat_runtime/assets/help/conversations.html +105 -0
  27. zerochat_runtime/assets/help/debug.html +108 -0
  28. zerochat_runtime/assets/help/en/architecture.html +247 -0
  29. zerochat_runtime/assets/help/en/composer.html +134 -0
  30. zerochat_runtime/assets/help/en/conversations.html +104 -0
  31. zerochat_runtime/assets/help/en/debug.html +105 -0
  32. zerochat_runtime/assets/help/en/gemini-free.html +39 -0
  33. zerochat_runtime/assets/help/en/index.html +191 -0
  34. zerochat_runtime/assets/help/en/learning.html +71 -0
  35. zerochat_runtime/assets/help/en/mcp.html +143 -0
  36. zerochat_runtime/assets/help/en/openrouter-free.html +39 -0
  37. zerochat_runtime/assets/help/en/profiles.html +173 -0
  38. zerochat_runtime/assets/help/en/rag.html +130 -0
  39. zerochat_runtime/assets/help/en/reasoning-telemetry.html +108 -0
  40. zerochat_runtime/assets/help/en/tools-agent.html +133 -0
  41. zerochat_runtime/assets/help/en/webllm.html +355 -0
  42. zerochat_runtime/assets/help/gemini-free.html +39 -0
  43. zerochat_runtime/assets/help/help.css +604 -0
  44. zerochat_runtime/assets/help/help.js +51 -0
  45. zerochat_runtime/assets/help/index.html +194 -0
  46. zerochat_runtime/assets/help/learning.html +71 -0
  47. zerochat_runtime/assets/help/mcp.html +143 -0
  48. zerochat_runtime/assets/help/openrouter-free.html +39 -0
  49. zerochat_runtime/assets/help/profiles.html +174 -0
  50. zerochat_runtime/assets/help/rag.html +130 -0
  51. zerochat_runtime/assets/help/reasoning-telemetry.html +111 -0
  52. zerochat_runtime/assets/help/tools-agent.html +133 -0
  53. zerochat_runtime/assets/help/webllm.html +359 -0
  54. zerochat_runtime/assets/js/agent-core.js +1553 -0
  55. zerochat_runtime/assets/js/api.js +844 -0
  56. zerochat_runtime/assets/js/app.js +2558 -0
  57. zerochat_runtime/assets/js/attachments.js +220 -0
  58. zerochat_runtime/assets/js/charts.js +338 -0
  59. zerochat_runtime/assets/js/chat-engine.js +566 -0
  60. zerochat_runtime/assets/js/config-store.js +207 -0
  61. zerochat_runtime/assets/js/context-manager.js +584 -0
  62. zerochat_runtime/assets/js/conversation-service.js +484 -0
  63. zerochat_runtime/assets/js/cookies.js +688 -0
  64. zerochat_runtime/assets/js/data-reset-service.js +136 -0
  65. zerochat_runtime/assets/js/debug.js +508 -0
  66. zerochat_runtime/assets/js/defaults.js +16 -0
  67. zerochat_runtime/assets/js/export.js +179 -0
  68. zerochat_runtime/assets/js/file-parser.js +2088 -0
  69. zerochat_runtime/assets/js/generation-controller.js +417 -0
  70. zerochat_runtime/assets/js/i18n.js +1483 -0
  71. zerochat_runtime/assets/js/icons.js +156 -0
  72. zerochat_runtime/assets/js/ingestionEngine.js +436 -0
  73. zerochat_runtime/assets/js/markdown.js +588 -0
  74. zerochat_runtime/assets/js/mcp.js +1271 -0
  75. zerochat_runtime/assets/js/message-turns.js +62 -0
  76. zerochat_runtime/assets/js/profile-backup.js +118 -0
  77. zerochat_runtime/assets/js/profile-export-bundle.js +481 -0
  78. zerochat_runtime/assets/js/profile-repository.js +213 -0
  79. zerochat_runtime/assets/js/providers-webllm.js +486 -0
  80. zerochat_runtime/assets/js/providers.js +1560 -0
  81. zerochat_runtime/assets/js/rag-index.js +295 -0
  82. zerochat_runtime/assets/js/rag-service.js +530 -0
  83. zerochat_runtime/assets/js/rag-ui.js +997 -0
  84. zerochat_runtime/assets/js/ragStorage.js +676 -0
  85. zerochat_runtime/assets/js/sandbox.js +449 -0
  86. zerochat_runtime/assets/js/state.js +704 -0
  87. zerochat_runtime/assets/js/storage-db.js +144 -0
  88. zerochat_runtime/assets/js/tool-cards.js +330 -0
  89. zerochat_runtime/assets/js/tool-security.js +653 -0
  90. zerochat_runtime/assets/js/tools/README.md +50 -0
  91. zerochat_runtime/assets/js/tools/builtin/agent-checkpoint.tool.js +212 -0
  92. zerochat_runtime/assets/js/tools/builtin/download-pdf.tool.js +111 -0
  93. zerochat_runtime/assets/js/tools/builtin/execute-javascript.tool.js +219 -0
  94. zerochat_runtime/assets/js/tools/builtin/fetch-web-page.tool.js +112 -0
  95. zerochat_runtime/assets/js/tools/builtin/list-documents.tool.js +117 -0
  96. zerochat_runtime/assets/js/tools/builtin/read-knowledge-chunk.tool.js +129 -0
  97. zerochat_runtime/assets/js/tools/builtin/read-knowledge-image.tool.js +95 -0
  98. zerochat_runtime/assets/js/tools/builtin/render-chart.tool.js +90 -0
  99. zerochat_runtime/assets/js/tools/builtin/search-knowledge-base.tool.js +124 -0
  100. zerochat_runtime/assets/js/tools/builtin/search-web.tool.js +125 -0
  101. zerochat_runtime/assets/js/tools/tool-manifest.js +53 -0
  102. zerochat_runtime/assets/js/tools/tool-runtime.js +77 -0
  103. zerochat_runtime/assets/js/ui-composer.js +319 -0
  104. zerochat_runtime/assets/js/ui-conversation.js +655 -0
  105. zerochat_runtime/assets/js/ui-dialogs.js +132 -0
  106. zerochat_runtime/assets/js/ui-generation-status.js +119 -0
  107. zerochat_runtime/assets/js/ui-inspector.js +804 -0
  108. zerochat_runtime/assets/js/ui-mcp.js +604 -0
  109. zerochat_runtime/assets/js/ui-profiles.js +824 -0
  110. zerochat_runtime/assets/js/ui-reasoning.js +146 -0
  111. zerochat_runtime/assets/js/ui-settings.js +825 -0
  112. zerochat_runtime/assets/js/ui-shell.js +215 -0
  113. zerochat_runtime/assets/js/ui-sidebar.js +429 -0
  114. zerochat_runtime/assets/js/ui-telemetry.js +404 -0
  115. zerochat_runtime/assets/js/ui-transfer.js +262 -0
  116. zerochat_runtime/assets/js/utils.js +216 -0
  117. zerochat_runtime/assets/js/vendor/orama.browser.js +11 -0
  118. zerochat_runtime/assets/js/web-browser.js +569 -0
  119. zerochat_runtime/assets/js/web-search.js +519 -0
  120. zerochat_runtime/assets/manifest.webmanifest +20 -0
  121. zerochat_runtime/assets/services/dummy_mcp/dummy_mcp_server.py +47 -0
  122. zerochat_runtime/assets/services/dummy_mcp/service.json +22 -0
  123. zerochat_runtime/assets/services/lsp/installer.json +9 -0
  124. zerochat_runtime/assets/services/lsp/service.json +22 -0
  125. zerochat_runtime/assets/services/memory/installer.json +9 -0
  126. zerochat_runtime/assets/services/memory/service.json +24 -0
  127. zerochat_runtime/assets/services/playwright/installer.json +10 -0
  128. zerochat_runtime/assets/services/playwright/service.json +39 -0
  129. zerochat_runtime/assets/sw.js +164 -0
  130. zerochat_runtime/assets/zerochat.html +671 -0
@@ -0,0 +1,1553 @@
1
+ /**
2
+ * Módulo de Núcleo Agéntico y Sistema Modular de Herramientas (ChatAgentCore) para ZeroChat.
3
+ *
4
+ * Arquitectura:
5
+ * Model -> AgentCore -> ToolRegistry -> Tool -> ToolExecutor
6
+ *
7
+ * Preparado para MCP (Model Context Protocol) y herramientas extensibles.
8
+ * Compatible con entornos file://, http:// y Node.js.
9
+ */
10
+
11
+ (function (root, factory) {
12
+ if (typeof exports === 'object' && typeof module !== 'undefined') {
13
+ module.exports = factory();
14
+ } else {
15
+ root.ChatAgentCore = factory();
16
+ }
17
+ })(typeof self !== 'undefined' ? self : this, function () {
18
+ 'use strict';
19
+
20
+ // Resolutores perezosos de dependencias del entorno
21
+ function getBuiltinToolModule(globalName, modulePath) {
22
+ if (typeof window !== 'undefined' && window[globalName]) {
23
+ return window[globalName];
24
+ }
25
+ if (typeof require !== 'undefined') {
26
+ try { return require(modulePath); } catch (e) {}
27
+ }
28
+ return null;
29
+ }
30
+
31
+ function getBuiltinToolManifest() {
32
+ if (typeof window !== 'undefined' && window.ChatToolManifest) return window.ChatToolManifest.builtin;
33
+ if (typeof require !== 'undefined') {
34
+ try { return require('./tools/tool-manifest.js').builtin; } catch (e) {}
35
+ }
36
+ return null;
37
+ }
38
+
39
+ function createBuiltinTool(toolId, globalName, modulePath) {
40
+ const builtinManifest = getBuiltinToolManifest();
41
+ const toolModule = builtinManifest?.get(toolId) || getBuiltinToolModule(globalName, modulePath);
42
+ if (!toolModule || typeof toolModule.createTool !== 'function') {
43
+ throw new Error(`No se pudo cargar el módulo de la herramienta ${toolId}.`);
44
+ }
45
+ return toolModule.createTool(Tool);
46
+ }
47
+
48
+ function getAPI() {
49
+ if (typeof window !== 'undefined' && window.ChatAPI) return window.ChatAPI;
50
+ if (typeof require !== 'undefined') {
51
+ try { return require('./api.js'); } catch (e) {}
52
+ }
53
+ return null;
54
+ }
55
+
56
+ function getContextManager() {
57
+ if (typeof window !== 'undefined' && window.ChatContextManager) return window.ChatContextManager;
58
+ if (typeof require !== 'undefined') {
59
+ try { return require('./context-manager.js'); } catch (e) {}
60
+ }
61
+ return null;
62
+ }
63
+
64
+ function getToolRuntime() {
65
+ if (typeof window !== 'undefined' && window.ChatToolRuntime) return window.ChatToolRuntime;
66
+ if (typeof require !== 'undefined') {
67
+ try { return require('./tools/tool-runtime.js'); } catch (e) {}
68
+ }
69
+ return null;
70
+ }
71
+
72
+ function getToolSecurity() {
73
+ if (typeof window !== 'undefined' && window.ChatToolSecurity) return window.ChatToolSecurity;
74
+ if (typeof require !== 'undefined') {
75
+ try { return require('./tool-security.js'); } catch (e) {}
76
+ }
77
+ return null;
78
+ }
79
+
80
+ function getToolCards() {
81
+ if (typeof window !== 'undefined' && window.ChatToolCards) return window.ChatToolCards;
82
+ if (typeof require !== 'undefined') {
83
+ try { return require('./tool-cards.js'); } catch (e) {}
84
+ }
85
+ return null;
86
+ }
87
+
88
+ function getI18n() {
89
+ if (typeof window !== 'undefined' && window.ChatI18n) return window.ChatI18n;
90
+ if (typeof require !== 'undefined') {
91
+ try { return require('./i18n.js'); } catch (e) {}
92
+ }
93
+ return null;
94
+ }
95
+
96
+ /**
97
+ * Versión del contrato público que deben implementar las herramientas.
98
+ *
99
+ * Una herramienta puede exponerse como una instancia de Tool (adaptador de
100
+ * compatibilidad) o como un módulo que implemente los mismos métodos. Las
101
+ * futuras herramientas autocontenidas usarán este contrato directamente.
102
+ */
103
+ const TOOL_CONTRACT_VERSION = 1;
104
+
105
+ /**
106
+ * Resultado normalizado de una ejecución. Mantiene el resultado nativo en
107
+ * `data` para no alterar el comportamiento de las herramientas existentes.
108
+ */
109
+ class ToolOutcome {
110
+ constructor(options = {}) {
111
+ this.ok = options.ok !== false;
112
+ this.data = options.data === undefined ? null : options.data;
113
+ this.error = options.error || null;
114
+ this.meta = options.meta || {};
115
+ }
116
+
117
+ static fromExecution(result, meta = {}) {
118
+ const ok = result?.success !== false;
119
+ return new ToolOutcome({
120
+ ok,
121
+ data: result,
122
+ error: result?.error || null,
123
+ meta
124
+ });
125
+ }
126
+
127
+ static fromError(error, meta = {}) {
128
+ return new ToolOutcome({
129
+ ok: false,
130
+ data: null,
131
+ error: error?.message || String(error || 'Error de ejecución de herramienta.'),
132
+ meta
133
+ });
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Valida el contrato mínimo de una herramienta antes de registrarla.
139
+ * La validación no impone todavía una vista específica: durante la migración
140
+ * las herramientas existentes pueden usar el renderer genérico.
141
+ */
142
+ function validateToolContract(tool) {
143
+ const errors = [];
144
+ if (!tool || typeof tool !== 'object') {
145
+ return { valid: false, errors: ['La herramienta debe ser un objeto.'] };
146
+ }
147
+ if (!tool.id || typeof tool.id !== 'string') errors.push('Falta un id de herramienta válido.');
148
+ if (!tool.name || typeof tool.name !== 'string') errors.push('Falta un nombre de función válido.');
149
+ if (!tool.description || typeof tool.description !== 'string') errors.push('Falta una descripción válida.');
150
+ if (!tool.parameters || tool.parameters.type !== 'object') errors.push('El esquema parameters debe ser un objeto JSON Schema.');
151
+ if (typeof tool.getDefinition !== 'function') errors.push('Falta getDefinition().');
152
+ if (typeof tool.execute !== 'function') errors.push('Falta execute(args, context).');
153
+ if (typeof tool.isAvailable !== 'function') errors.push('Falta isAvailable(context).');
154
+ if (typeof tool.serializeResultForModel !== 'function') errors.push('Falta serializeResultForModel(args, result).');
155
+ if (typeof tool.formatMarkdownResult !== 'function') errors.push('Falta formatMarkdownResult(args, result).');
156
+ if (typeof tool.formatDispatchMarkdown !== 'function') errors.push('Falta formatDispatchMarkdown(args, result).');
157
+ if (!tool.settings || typeof tool.settings !== 'object') errors.push('Falta descriptor settings.');
158
+
159
+ return { valid: errors.length === 0, errors };
160
+ }
161
+
162
+ /**
163
+ * Representa una herramienta individual ejecutable y adapta la API histórica
164
+ * al contrato ToolModule v1.
165
+ */
166
+ class Tool {
167
+ constructor(options = {}) {
168
+ const definition = options.definition || {};
169
+ this.contractVersion = options.contractVersion || TOOL_CONTRACT_VERSION;
170
+ this.id = options.id || definition.id || definition.name || options.name || '';
171
+ this.name = definition.name || options.name || '';
172
+ // Las tools históricas podían omitir descripción; el adaptador conserva
173
+ // su registro y les proporciona una descripción mínima válida.
174
+ this.description = definition.description || options.description || this.name;
175
+ this.parameters = definition.parameters || options.parameters || { type: 'object', properties: {} };
176
+ this.aliases = Array.isArray(options.aliases) ? options.aliases : [];
177
+ this.category = options.category || 'general';
178
+ this.metadata = options.metadata || {};
179
+ const settings = options.settings || {};
180
+ this.settings = {
181
+ titleKey: settings.titleKey || `agent_${this.name}_title`,
182
+ titleFallback: settings.titleFallback || options.metadata?.label || this.name,
183
+ descKey: settings.descKey || `agent_${this.name}_desc`,
184
+ descFallback: settings.descFallback || this.description,
185
+ icon: settings.icon || options.metadata?.icon || '⚙️',
186
+ defaultEnabled: settings.defaultEnabled !== false,
187
+ showInSettings: settings.showInSettings !== false
188
+ };
189
+ this.executeHandler = options.execute || null;
190
+ this.result = options.result || {};
191
+ this.formatter = options.formatter || null;
192
+ this.promptGuide = options.promptGuide || options.getSystemPromptGuide || null;
193
+ this.isAvailable = typeof options.isAvailable === 'function' ? options.isAvailable : (() => true);
194
+ this.displayMode = options.displayMode || options.view?.displayMode || options.metadata?.displayMode || options.definition?.displayMode || 'collapsed';
195
+ this.view = options.view ? { displayMode: this.displayMode, ...options.view } : { id: this.name, displayMode: this.displayMode };
196
+ }
197
+
198
+ /**
199
+ * Devuelve la definición en formato estándar Function Calling (OpenAI, Claude, Gemini).
200
+ */
201
+ getDefinition() {
202
+ return {
203
+ type: 'function',
204
+ function: {
205
+ name: this.name,
206
+ description: this.description,
207
+ parameters: this.parameters
208
+ }
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Devuelve la línea descriptiva de la herramienta para el System Prompt (guía de texto plano).
214
+ */
215
+ getSystemPromptGuide(lang = 'es') {
216
+ if (typeof this.promptGuide === 'function') {
217
+ return this.promptGuide(lang);
218
+ }
219
+ return `- \`${this.name}\`: ${this.description}`;
220
+ }
221
+
222
+ /**
223
+ * Ejecuta la herramienta asíncronamente.
224
+ */
225
+ async execute(args, context = {}) {
226
+ if (typeof this.executeHandler === 'function') {
227
+ return this.executeHandler(args, context);
228
+ }
229
+ throw new Error(`La herramienta ${this.name} no tiene handler de ejecución implementado.`);
230
+ }
231
+
232
+ /**
233
+ * Serializa una salida para el mensaje con rol `tool` que consume el LLM.
234
+ */
235
+ serializeResultForModel(args, result, outcome) {
236
+ if (typeof this.result.toModel === 'function') {
237
+ return this.result.toModel(args, result, outcome);
238
+ }
239
+ return typeof result === 'object' ? JSON.stringify(result) : String(result ?? '');
240
+ }
241
+
242
+ /**
243
+ * Formatea el resultado en Markdown para su inserción en el historial o exportación.
244
+ */
245
+ formatMarkdownResult(args, result, outcome) {
246
+ if (typeof this.formatter === 'function') {
247
+ return this.formatter(args, result, outcome);
248
+ }
249
+ const output = typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result);
250
+ return `> ⚙️ **${this.name}**\n> \`\`\`json\n> ${output.split('\n').join('\n> ')}\n> \`\`\``;
251
+ }
252
+
253
+ /**
254
+ * Formato resumido que se añade al despacho y a la exportación de chat.
255
+ * Puede diferir del formato detallado conservado por la API histórica.
256
+ */
257
+ formatDispatchMarkdown(args, result, outcome) {
258
+ if (typeof this.result.toMarkdown === 'function') {
259
+ return this.result.toMarkdown(args, result, outcome);
260
+ }
261
+ return this.formatMarkdownResult(args, result, outcome);
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Clase base para proveedores de herramientas (ToolProvider).
267
+ */
268
+ class BaseToolProvider {
269
+ constructor(options = {}) {
270
+ this.id = options.id || 'base';
271
+ this.name = options.name || 'Base Tool Provider';
272
+ }
273
+
274
+ /**
275
+ * Devuelve el array de instancias Tool provistas por este proveedor.
276
+ */
277
+ getTools() {
278
+ return [];
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Proveedor de herramientas nativas de ZeroChat (BuiltinToolProvider).
284
+ * Provee las herramientas nativas registradas en módulos autocontenidos.
285
+ */
286
+ class BuiltinToolProvider extends BaseToolProvider {
287
+ constructor() {
288
+ super({ id: 'builtin', name: 'ZeroChat Built-in Tools' });
289
+ }
290
+
291
+ getTools() {
292
+ const tools = [];
293
+
294
+ // 1. Herramienta execute_javascript (módulo autocontenido)
295
+ tools.push(createBuiltinTool('execute_javascript', 'ChatBuiltinExecuteJavascriptTool', './tools/builtin/execute-javascript.tool.js'));
296
+
297
+ // 2-4. Herramientas web autocontenidas
298
+ tools.push(createBuiltinTool('search_web', 'ChatBuiltinSearchWebTool', './tools/builtin/search-web.tool.js'));
299
+ tools.push(createBuiltinTool('fetch_web_page', 'ChatBuiltinFetchWebPageTool', './tools/builtin/fetch-web-page.tool.js'));
300
+ tools.push(createBuiltinTool('download_pdf', 'ChatBuiltinDownloadPdfTool', './tools/builtin/download-pdf.tool.js'));
301
+
302
+ // 5-8. Gráficos y RAG como módulos autocontenidos.
303
+ tools.push(createBuiltinTool('render_chart', 'ChatBuiltinRenderChartTool', './tools/builtin/render-chart.tool.js'));
304
+ tools.push(createBuiltinTool('list_documents', 'ChatBuiltinListDocumentsTool', './tools/builtin/list-documents.tool.js'));
305
+ tools.push(createBuiltinTool('search_knowledge_base', 'ChatBuiltinSearchKnowledgeBaseTool', './tools/builtin/search-knowledge-base.tool.js'));
306
+ tools.push(createBuiltinTool('read_knowledge_chunk', 'ChatBuiltinReadKnowledgeChunkTool', './tools/builtin/read-knowledge-chunk.tool.js'));
307
+ tools.push(createBuiltinTool('read_knowledge_image', 'ChatBuiltinReadKnowledgeImageTool', './tools/builtin/read-knowledge-image.tool.js'));
308
+
309
+ // 9. Punto de control agéntico multipropósito
310
+ tools.push(createBuiltinTool('agent_checkpoint', 'ChatBuiltinAgentCheckpointTool', './tools/builtin/agent-checkpoint.tool.js'));
311
+
312
+ return tools;
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Registro central de herramientas y proveedores (ToolRegistry).
318
+ */
319
+ class ToolRegistry {
320
+ constructor() {
321
+ this.tools = new Map();
322
+ this.aliasMap = new Map();
323
+ this.providers = new Map();
324
+
325
+ // Registrar proveedor de herramientas nativas por defecto
326
+ this.registerProvider(new BuiltinToolProvider());
327
+ }
328
+
329
+ /**
330
+ * Registra un proveedor de herramientas.
331
+ */
332
+ registerProvider(provider) {
333
+ if (!provider || !provider.id) return;
334
+ this.providers.set(provider.id, provider);
335
+ const tools = provider.getTools();
336
+ if (Array.isArray(tools)) {
337
+ tools.forEach(tool => this.registerTool(tool));
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Registra una herramienta individual.
343
+ */
344
+ registerTool(tool) {
345
+ const validation = validateToolContract(tool);
346
+ if (!validation.valid) {
347
+ throw new Error(`Contrato de herramienta inválido: ${validation.errors.join(' ')}`);
348
+ }
349
+ const canonicalName = tool.name.trim().toLowerCase();
350
+ this.tools.set(canonicalName, tool);
351
+
352
+ if (tool.category === 'mcp') return tool;
353
+
354
+ // Mapear nombre canónico normalizado sin guiones bajos
355
+ this.aliasMap.set(canonicalName.replace(/_/g, ''), canonicalName);
356
+
357
+ // Mapear alias declarados
358
+ if (Array.isArray(tool.aliases)) {
359
+ tool.aliases.forEach(alias => {
360
+ const cleanAlias = String(alias).trim().toLowerCase();
361
+ this.aliasMap.set(cleanAlias, canonicalName);
362
+ this.aliasMap.set(cleanAlias.replace(/_/g, ''), canonicalName);
363
+ });
364
+ }
365
+ return tool;
366
+ }
367
+
368
+ /**
369
+ * Obtiene una herramienta por su nombre canónico o alias.
370
+ */
371
+ getTool(rawName) {
372
+ if (!rawName) return null;
373
+ const clean = String(rawName).trim().toLowerCase();
374
+ if (this.tools.has(clean)) {
375
+ return this.tools.get(clean);
376
+ }
377
+ const canonical = this.aliasMap.get(clean) || this.aliasMap.get(clean.replace(/_/g, ''));
378
+ if (canonical && this.tools.has(canonical) && this.tools.get(canonical).category !== 'mcp') {
379
+ return this.tools.get(canonical);
380
+ }
381
+ return null;
382
+ }
383
+
384
+ /**
385
+ * Comprueba si una herramienta está registrada.
386
+ */
387
+ hasTool(name) {
388
+ return !!this.getTool(name);
389
+ }
390
+
391
+ /**
392
+ * Desregistra un proveedor y todas las herramientas que expone.
393
+ */
394
+ unregisterProvider(providerId) {
395
+ if (!providerId || !this.providers.has(providerId)) return;
396
+ const provider = this.providers.get(providerId);
397
+ this.providers.delete(providerId);
398
+ const tools = typeof provider.getTools === 'function' ? provider.getTools() : [];
399
+ if (Array.isArray(tools)) {
400
+ tools.forEach(tool => {
401
+ if (tool && tool.name) {
402
+ this.unregisterTool(tool.name);
403
+ }
404
+ });
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Desregistra una herramienta individual y sus alias.
410
+ */
411
+ unregisterTool(name) {
412
+ if (!name) return;
413
+ const clean = String(name).trim().toLowerCase();
414
+ const tool = this.tools.get(clean);
415
+ if (tool) {
416
+ this.tools.delete(clean);
417
+ this.aliasMap.delete(clean.replace(/_/g, ''));
418
+ if (Array.isArray(tool.aliases)) {
419
+ tool.aliases.forEach(alias => {
420
+ const cleanAlias = String(alias).trim().toLowerCase();
421
+ this.aliasMap.delete(cleanAlias);
422
+ this.aliasMap.delete(cleanAlias.replace(/_/g, ''));
423
+ });
424
+ }
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Devuelve las herramientas visibles para la UI de Configuración.
430
+ * @param {string} [filterCategory=null] - Opcional: filtrar por categoría ('builtin', 'mcp', etc.)
431
+ */
432
+ listToolsForUI(filterCategory = null) {
433
+ const list = [];
434
+ for (const tool of this.tools.values()) {
435
+ if (tool.settings && tool.settings.showInSettings !== false) {
436
+ if (filterCategory && tool.category !== filterCategory) {
437
+ continue;
438
+ }
439
+ list.push({
440
+ id: tool.id || tool.name,
441
+ name: tool.name,
442
+ category: tool.category,
443
+ icon: tool.settings.icon,
444
+ titleKey: tool.settings.titleKey,
445
+ titleFallback: tool.settings.titleFallback,
446
+ descKey: tool.settings.descKey,
447
+ descFallback: tool.settings.descFallback,
448
+ defaultEnabled: tool.settings.defaultEnabled !== false
449
+ });
450
+ }
451
+ }
452
+ return list;
453
+ }
454
+
455
+ /**
456
+ * Resuelve las definiciones Function Calling de las herramientas activas según la configuración.
457
+ */
458
+ getActiveDefinitions(appConfig = {}) {
459
+ const defs = [];
460
+ const enabledTools = appConfig.enabledTools || {};
461
+
462
+ for (const [name, tool] of this.tools.entries()) {
463
+ // 1. Comprobar si la herramienta está disponible por su contexto (ej. RAG requiere rama activa)
464
+ if (typeof tool.isAvailable === 'function' && !tool.isAvailable(appConfig)) {
465
+ continue;
466
+ }
467
+
468
+ // 2. Si la herramienta se configura en la UI, verificar si está activada
469
+ if (tool.settings && tool.settings.showInSettings !== false) {
470
+ const toolId = tool.id || tool.name;
471
+ const isEnabled = enabledTools[toolId] !== undefined
472
+ ? enabledTools[toolId] !== false
473
+ : (enabledTools[tool.name] !== undefined ? enabledTools[tool.name] !== false : tool.settings.defaultEnabled !== false);
474
+
475
+ if (!isEnabled) {
476
+ continue;
477
+ }
478
+ }
479
+
480
+ defs.push(tool.getDefinition());
481
+ }
482
+ return defs;
483
+ }
484
+
485
+ /**
486
+ * Alias compatible con versiones anteriores de getDefinitions.
487
+ */
488
+ getDefinitions(filterOptions = {}) {
489
+ return this.getActiveDefinitions(filterOptions);
490
+ }
491
+
492
+ /**
493
+ * Genera la guía textual de herramientas para el System Prompt.
494
+ */
495
+ getActivePromptGuide(appConfig = {}) {
496
+ const enabledTools = appConfig.enabledTools || {};
497
+ const guides = [];
498
+
499
+ for (const tool of this.tools.values()) {
500
+ if (typeof tool.isAvailable === 'function' && !tool.isAvailable(appConfig)) {
501
+ continue;
502
+ }
503
+
504
+ if (tool.settings && tool.settings.showInSettings !== false) {
505
+ const toolId = tool.id || tool.name;
506
+ const isEnabled = enabledTools[toolId] !== undefined
507
+ ? enabledTools[toolId] !== false
508
+ : (enabledTools[tool.name] !== undefined ? enabledTools[tool.name] !== false : tool.settings.defaultEnabled !== false);
509
+
510
+ if (!isEnabled) {
511
+ continue;
512
+ }
513
+ }
514
+
515
+ if (typeof tool.getSystemPromptGuide === 'function') {
516
+ const guideStr = tool.getSystemPromptGuide('en');
517
+ if (guideStr && typeof guideStr === 'string') {
518
+ guides.push(guideStr);
519
+ }
520
+ }
521
+ }
522
+
523
+ if (guides.length === 0) return '';
524
+
525
+ return `\n\n[AVAILABLE TOOLS AND FUNCTIONS]:\nYou can use the following tools when needed to answer accurately:\n${guides.join('\n')}\n*Workflow instruction:* After using tools, answer the user's question directly, clearly, and concisely. Use findings only as evidence, citing sources briefly or via inline links. Avoid lengthy or redundant summaries of consulted sources and do not show raw tool output.`;
526
+ }
527
+
528
+ /**
529
+ * Lista todas las herramientas registradas con sus metadatos.
530
+ */
531
+ listTools() {
532
+ return Array.from(this.tools.values());
533
+ }
534
+ }
535
+
536
+ /**
537
+ * Motor de ejecución seguro para llamadas a herramientas (ToolExecutor).
538
+ */
539
+ class ToolExecutor {
540
+ constructor(registry) {
541
+ this.registry = registry || new ToolRegistry();
542
+ }
543
+
544
+ /**
545
+ * Parsea tolerante y seguramente los argumentos de una llamada a herramienta.
546
+ */
547
+ parseArguments(rawArgs) {
548
+ if (!rawArgs) return {};
549
+ if (typeof rawArgs === 'object' && rawArgs !== null) return rawArgs;
550
+
551
+ const str = String(rawArgs).trim();
552
+ try {
553
+ return JSON.parse(str);
554
+ } catch (e) {
555
+ // Fallback tolerante para formato clave: valor o texto plano
556
+ const urlMatch = str.match(/(?:url|link|href)\s*[:=]\s*["']?([^"'\s,}]+)/i);
557
+ const queryMatch = str.match(/(?:query|q|search)\s*[:=]\s*["']?([^"'\r\n,}]+)/i);
558
+ const codeMatch = str.match(/(?:code|js|javascript)\s*[:=]\s*["']?([^"'\r\n]+)/i);
559
+
560
+ if (urlMatch) return { url: urlMatch[1] };
561
+ if (queryMatch) return { query: queryMatch[1].trim() };
562
+ if (codeMatch) return { code: codeMatch[1].trim().replace(/["']$/, '') };
563
+
564
+ return { input: str };
565
+ }
566
+ }
567
+
568
+ /**
569
+ * Aplica la política de ejecución antes de invocar una herramienta. La UI
570
+ * puede aportar `requestToolAuthorization`, pero no puede omitir este
571
+ * punto de control.
572
+ * @private
573
+ */
574
+ async _authorizeToolCall(tool, args, toolCall, context) {
575
+ const ToolSecurity = getToolSecurity();
576
+ const I18n = getI18n();
577
+ const t = (key, fallback) => (I18n?.t ? I18n.t(key) : fallback);
578
+ const authEval = ToolSecurity?.manager?.evaluateAuthorization
579
+ ? ToolSecurity.manager.evaluateAuthorization(tool, args, context)
580
+ : { status: 'allow', requiresApproval: false };
581
+
582
+ if (authEval.status === 'deny') {
583
+ return { allowed: false, error: t('tool_security_policy_blocked', 'Herramienta bloqueada por política de seguridad.') };
584
+ }
585
+
586
+ if (!authEval.requiresApproval) {
587
+ return { allowed: true };
588
+ }
589
+
590
+ if (typeof context.requestToolAuthorization !== 'function') {
591
+ return { allowed: false, error: 'La herramienta requiere autorización explícita del usuario.' };
592
+ }
593
+
594
+ const decision = await context.requestToolAuthorization(toolCall, {
595
+ args,
596
+ serverName: authEval.serverName,
597
+ toolName: tool.name,
598
+ signal: context.signal
599
+ });
600
+ const decisionType = (typeof decision === 'object' && decision !== null) ? decision.decision : decision;
601
+
602
+ if (context.signal?.aborted) return { allowed: false, error: 'Tool execution cancelled.' };
603
+ if (ToolSecurity?.manager?.evaluateAuthorization(tool, args, context).status === 'deny') {
604
+ return { allowed: false, error: t('tool_security_policy_blocked', 'Tool blocked by security policy.') };
605
+ }
606
+
607
+ if (decisionType !== 'allow_once' && decisionType !== 'allow_always') {
608
+ return { allowed: false, error: t('tool_auth_denied_msg', 'Ejecución denegada por el usuario.') };
609
+ }
610
+
611
+ if (decisionType === 'allow_always') {
612
+ ToolSecurity.manager.setToolPolicy(authEval.toolId || tool.name, 'allow', {
613
+ serverName: authEval.serverName,
614
+ originalName: authEval.originalName,
615
+ constraints: (typeof decision === 'object' && decision !== null) ? (decision.constraints || null) : null
616
+ });
617
+ }
618
+
619
+ return { allowed: true };
620
+ }
621
+
622
+ /**
623
+ * Ejecuta una llamada a herramienta con control de tiempo, métricas y manejo de errores.
624
+ */
625
+ async executeToolCall(toolCall, context = {}) {
626
+ const rawName = toolCall?.function?.name || '';
627
+ const tool = this.registry.getTool(rawName);
628
+ const parsedArgs = this.parseArguments(toolCall?.function?.arguments);
629
+
630
+ if (!tool) {
631
+ const error = `Herramienta '${rawName}' no encontrada en el registro.`;
632
+ return {
633
+ success: false,
634
+ toolName: rawName,
635
+ error,
636
+ executionTimeMs: 0,
637
+ result: null,
638
+ outcome: ToolOutcome.fromError(error, {
639
+ toolId: null,
640
+ toolName: rawName,
641
+ contractVersion: TOOL_CONTRACT_VERSION,
642
+ executionTimeMs: 0
643
+ })
644
+ };
645
+ }
646
+
647
+ const displayMode = tool.displayMode || tool.view?.displayMode || 'collapsed';
648
+ const startTime = typeof performance !== 'undefined' ? performance.now() : Date.now();
649
+
650
+ try {
651
+ const authorization = await this._authorizeToolCall(tool, parsedArgs, toolCall, context);
652
+ if (!authorization.allowed) {
653
+ return {
654
+ success: false,
655
+ tool,
656
+ toolName: tool.name,
657
+ displayMode,
658
+ args: parsedArgs,
659
+ error: authorization.error,
660
+ executionTimeMs: 0,
661
+ result: null,
662
+ outcome: ToolOutcome.fromError(authorization.error, {
663
+ toolId: tool.id,
664
+ toolName: tool.name,
665
+ contractVersion: tool.contractVersion || TOOL_CONTRACT_VERSION,
666
+ executionTimeMs: 0
667
+ })
668
+ };
669
+ }
670
+ if (context.signal && context.signal.aborted) {
671
+ throw new Error('Ejecución de herramienta cancelada por el usuario.');
672
+ }
673
+
674
+ const ToolRuntime = getToolRuntime();
675
+ const executionContext = ToolRuntime?.createToolExecutionContext
676
+ ? ToolRuntime.createToolExecutionContext({ displayMode, ...context })
677
+ : { displayMode, ...context };
678
+ const execResult = await tool.execute(parsedArgs, executionContext);
679
+ const endTime = typeof performance !== 'undefined' ? performance.now() : Date.now();
680
+ const elapsed = parseFloat((endTime - startTime).toFixed(2));
681
+
682
+ const meta = {
683
+ toolId: tool.id,
684
+ toolName: tool.name,
685
+ contractVersion: tool.contractVersion || TOOL_CONTRACT_VERSION,
686
+ executionTimeMs: elapsed,
687
+ displayMode
688
+ };
689
+ return {
690
+ success: execResult?.success !== false,
691
+ tool: tool,
692
+ toolName: tool.name,
693
+ displayMode,
694
+ args: parsedArgs,
695
+ result: execResult,
696
+ outcome: ToolOutcome.fromExecution(execResult, meta),
697
+ executionTimeMs: elapsed,
698
+ error: execResult?.error
699
+ };
700
+ } catch (err) {
701
+ const endTime = typeof performance !== 'undefined' ? performance.now() : Date.now();
702
+ const elapsed = parseFloat((endTime - startTime).toFixed(2));
703
+
704
+ const meta = {
705
+ toolId: tool.id,
706
+ toolName: tool.name,
707
+ contractVersion: tool.contractVersion || TOOL_CONTRACT_VERSION,
708
+ executionTimeMs: elapsed,
709
+ displayMode
710
+ };
711
+ return {
712
+ success: false,
713
+ tool: tool,
714
+ toolName: tool.name,
715
+ displayMode,
716
+ args: parsedArgs,
717
+ error: err.message || String(err),
718
+ executionTimeMs: elapsed,
719
+ result: null,
720
+ outcome: ToolOutcome.fromError(err, meta)
721
+ };
722
+ }
723
+ }
724
+
725
+ /**
726
+ * Despacha una llamada a herramienta gestionando el ciclo completo:
727
+ * - Parseo seguro de argumentos
728
+ * - Creación inicial e inserción de la tarjeta DOM en vivo
729
+ * - Logging previo a consola de depuración
730
+ * - Ejecución asíncrona a través de executeToolCall
731
+ * - Actualización reactiva de la tarjeta DOM con los resultados
732
+ * - Formateo de la respuesta textual para el historial del chat y markdown
733
+ */
734
+ async dispatchToolCall(toolCall, options = {}) {
735
+ const {
736
+ container,
737
+ onLog,
738
+ attachListeners,
739
+ scrollToBottom,
740
+ language = 'es'
741
+ } = options;
742
+
743
+ const rawFuncName = toolCall?.function?.name || '';
744
+ const parsedArgs = this.parseArguments(toolCall?.function?.arguments);
745
+ const ToolCards = getToolCards();
746
+
747
+ // 1. Crear e insertar la tarjeta DOM en vivo con estado de carga
748
+ let cardEl = null;
749
+ if (ToolCards && ToolCards.createLiveToolCard && container && typeof document !== 'undefined') {
750
+ cardEl = ToolCards.createLiveToolCard(rawFuncName, parsedArgs);
751
+ if (cardEl) {
752
+ container.appendChild(cardEl);
753
+ if (typeof attachListeners === 'function') attachListeners(cardEl);
754
+ if (typeof scrollToBottom === 'function') scrollToBottom();
755
+ }
756
+ }
757
+
758
+ // 2. Logging previo
759
+ if (typeof onLog === 'function') {
760
+ onLog('tool', `${rawFuncName}:\n${JSON.stringify(parsedArgs, null, 2)}`);
761
+ onLog('raw', `>>> TOOL CALL ${rawFuncName}:\n${JSON.stringify(parsedArgs, null, 2)}`);
762
+ }
763
+
764
+ // 3. Ejecutar la herramienta a través de executeToolCall
765
+ const execRes = await this.executeToolCall(toolCall, {
766
+ lang: language,
767
+ ...options,
768
+ requestToolAuthorization: ToolCards?.promptToolAuthorization
769
+ ? async (pendingToolCall, authorization) => {
770
+ if (typeof scrollToBottom === 'function') scrollToBottom();
771
+ return ToolCards.promptToolAuthorization(cardEl, pendingToolCall, authorization);
772
+ }
773
+ : undefined
774
+ });
775
+
776
+ // 4. Actualizar la tarjeta DOM con el resultado
777
+ if (ToolCards && ToolCards.updateLiveToolCard && cardEl) {
778
+ ToolCards.updateLiveToolCard(cardEl, rawFuncName, parsedArgs, execRes.result || execRes, execRes.executionTimeMs, {
779
+ displayMode: execRes.displayMode || execRes.tool?.displayMode,
780
+ outcome: execRes.outcome
781
+ });
782
+ if (typeof attachListeners === 'function') attachListeners(cardEl);
783
+ if (typeof scrollToBottom === 'function') scrollToBottom();
784
+ }
785
+
786
+ // 5. Formatear la salida para el mensaje `tool` sin conocer tipos concretos.
787
+ const resolvedTool = execRes.tool;
788
+ let resultText = '';
789
+ try {
790
+ const serialized = resolvedTool
791
+ ? resolvedTool.serializeResultForModel(parsedArgs, execRes.result, execRes.outcome)
792
+ : (execRes.error || 'Error de ejecución de herramienta.');
793
+ resultText = typeof serialized === 'string' ? serialized : JSON.stringify(serialized);
794
+ } catch (err) {
795
+ resultText = `Error al serializar el resultado de ${rawFuncName}: ${err.message || String(err)}`;
796
+ }
797
+
798
+ // 6. Logging posterior
799
+ if (typeof onLog === 'function') {
800
+ onLog('tool', `${rawFuncName} output (${execRes.executionTimeMs || 0}ms):\n${String(resultText).substring(0, 300)}`);
801
+ onLog('raw', `<<< TOOL RESULT ${rawFuncName} (${execRes.executionTimeMs || 0}ms):\n${resultText}`);
802
+ }
803
+
804
+ // 7. Formatear bloque Markdown para portapapeles/exportación desde la tool.
805
+ let toolMd = '';
806
+ try {
807
+ toolMd = resolvedTool
808
+ ? resolvedTool.formatDispatchMarkdown(parsedArgs, execRes.result, execRes.outcome)
809
+ : `> ⚙️ **${rawFuncName}**\n> \`\`\`\n> ${String(resultText).slice(0, 300)}\n> \`\`\``;
810
+ } catch (err) {
811
+ toolMd = `> ❌ **${rawFuncName}**: ${err.message || String(err)}`;
812
+ }
813
+
814
+ return {
815
+ success: execRes.success !== false,
816
+ result: execRes.result,
817
+ resultText,
818
+ markdownBlock: toolMd,
819
+ cardElement: cardEl,
820
+ executionTimeMs: execRes.executionTimeMs,
821
+ error: execRes.error,
822
+ toolName: rawFuncName,
823
+ args: parsedArgs
824
+ };
825
+ }
826
+ }
827
+
828
+ /**
829
+ * Entorno de ejecución agéntico avanzado (AgentRuntime).
830
+ * Orquesta el ciclo de vida de razonamiento y acción (ReAct), múltiples pasos,
831
+ * control de tiempo/timeout, reintentos de herramientas, cancelación,
832
+ * detección de bucles infinitos, presupuestos de tokens y síntesis final.
833
+ */
834
+ class AgentRuntime {
835
+ constructor(options = {}) {
836
+ this.registry = options.registry || new ToolRegistry();
837
+ this.executor = options.executor || new ToolExecutor(this.registry);
838
+ this.maxSteps = options.maxSteps || options.maxTurns || (options.appConfig && options.appConfig.maxAgentTurns) || (options.config && options.config.maxAgentTurns) || 15;
839
+ this.timeoutMs = options.timeoutMs || 0; // 0 = sin límite global de tiempo
840
+ this.stepTimeoutMs = options.stepTimeoutMs || 60000; // 60s por paso
841
+ this.maxRetries = options.maxRetries !== undefined ? options.maxRetries : 1;
842
+ this.loopThreshold = options.loopThreshold || 2;
843
+ this.autoSynthesize = options.autoSynthesize !== false;
844
+ }
845
+
846
+ /**
847
+ * Genera una huella única para la llamada a herramienta para detectar repeticiones cíclicas.
848
+ */
849
+ getToolCallFingerprint(toolCall) {
850
+ if (!toolCall || !toolCall.function) return '';
851
+ const name = toolCall.function.name || '';
852
+ const args = typeof toolCall.function.arguments === 'object'
853
+ ? JSON.stringify(toolCall.function.arguments)
854
+ : String(toolCall.function.arguments || '').trim();
855
+ return `${name}:${args}`;
856
+ }
857
+
858
+ /**
859
+ * Ejecuta una llamada a herramienta con soporte de reintentos y captura de errores.
860
+ */
861
+ async executeToolWithRetries(toolCall, context = {}, maxRetries = 0, onRetry = null) {
862
+ let attempts = 0;
863
+ let lastResult = null;
864
+
865
+ while (attempts <= maxRetries) {
866
+ if (context.signal && context.signal.aborted) {
867
+ return {
868
+ success: false,
869
+ toolName: toolCall?.function?.name || '',
870
+ error: 'Ejecución cancelada por el usuario.',
871
+ executionTimeMs: 0,
872
+ cancelled: true
873
+ };
874
+ }
875
+
876
+ lastResult = await this.executor.executeToolCall(toolCall, context);
877
+ if (lastResult.success) {
878
+ return lastResult;
879
+ }
880
+
881
+ attempts++;
882
+ if (attempts <= maxRetries && !lastResult.cancelled) {
883
+ if (typeof onRetry === 'function') {
884
+ onRetry(toolCall, attempts, maxRetries, lastResult.error);
885
+ }
886
+ // Pausa corta antes de reintentar
887
+ await new Promise(r => setTimeout(r, 50));
888
+ }
889
+ }
890
+
891
+ return lastResult;
892
+ }
893
+
894
+ /**
895
+ * Ejecuta el ciclo completo de ejecución agéntica multi-turno.
896
+ */
897
+ async execute(params = {}) {
898
+ const {
899
+ apiUrl,
900
+ apiType,
901
+ apiKey,
902
+ model,
903
+ messages = [],
904
+ temperature = 0.7,
905
+ reasoningEffort = 'medium',
906
+ reasoningTransport = 'auto',
907
+ enableTools = true,
908
+ toolFilterOptions = {},
909
+ signal = null,
910
+ maxSteps = this.maxSteps,
911
+ timeoutMs = this.timeoutMs,
912
+ stepTimeoutMs = this.stepTimeoutMs,
913
+ maxRetries = this.maxRetries,
914
+ loopThreshold = this.loopThreshold,
915
+ autoSynthesize = this.autoSynthesize,
916
+ synthesizeOnLoop = true,
917
+ api: customApi = null,
918
+ prepareMessages = null,
919
+ resolveToolDefinitions = null,
920
+ dispatchToolCall = null,
921
+ summarizeHistory = null,
922
+ contextOptions = {},
923
+ createMessageId = null,
924
+ appendFinalMessage = false,
925
+ onBeforeRequest = null,
926
+ callbacks = {}
927
+ } = params;
928
+
929
+ const API = customApi || getAPI();
930
+ if (!API || !API.streamChatCompletion) {
931
+ throw new Error('Módulo ChatAPI no disponible para ejecución agéntica.');
932
+ }
933
+
934
+ const ContextManager = getContextManager();
935
+ const safeContextOptions = contextOptions && typeof contextOptions === 'object'
936
+ ? contextOptions
937
+ : {};
938
+
939
+ const startTime = typeof performance !== 'undefined' ? performance.now() : Date.now();
940
+
941
+ // Configuración de cancelación y timeouts
942
+ let isTimedOut = false;
943
+ let isCancelled = false;
944
+ const internalAbortController = new AbortController();
945
+ let timeoutTimer = null;
946
+
947
+ if (timeoutMs > 0) {
948
+ timeoutTimer = setTimeout(() => {
949
+ isTimedOut = true;
950
+ internalAbortController.abort();
951
+ if (callbacks.onTimeout) {
952
+ callbacks.onTimeout(timeoutMs, stepIndex);
953
+ }
954
+ }, timeoutMs);
955
+ }
956
+
957
+ let onSignalAbort = null;
958
+ if (signal) {
959
+ if (signal.aborted) {
960
+ isCancelled = true;
961
+ internalAbortController.abort();
962
+ } else {
963
+ onSignalAbort = () => {
964
+ isCancelled = true;
965
+ internalAbortController.abort();
966
+ if (callbacks.onAbort) callbacks.onAbort();
967
+ };
968
+ signal.addEventListener('abort', onSignalAbort, { once: true });
969
+ }
970
+ }
971
+
972
+ const combinedSignal = internalAbortController.signal;
973
+
974
+ let stepIndex = 0;
975
+ let workingMessages = [...messages];
976
+ let finalAccumulatedText = '';
977
+ let finalReasoningText = '';
978
+ let lastStats = null;
979
+ const toolExecutions = [];
980
+ const toolCallSignatures = [];
981
+ let compressionUnavailable = false;
982
+ let status = 'completed';
983
+ let executionError = null;
984
+
985
+ while (stepIndex < maxSteps) {
986
+ if (combinedSignal.aborted) {
987
+ status = isTimedOut ? 'timeout' : 'cancelled';
988
+ executionError = new Error(isTimedOut ? 'Tiempo límite de ejecución agéntica excedido.' : 'Ejecución agéntica cancelada por el usuario.');
989
+ break;
990
+ }
991
+
992
+ if (callbacks.onStepStart) {
993
+ callbacks.onStepStart(stepIndex);
994
+ }
995
+
996
+ // Automatic context compaction is independent from the agent_checkpoint
997
+ // tool. It replaces the previous checkpoint and later dialogue atomically.
998
+ if (!compressionUnavailable && ContextManager && typeof ContextManager.shouldCompress === 'function' &&
999
+ typeof ContextManager.compressHistory === 'function' &&
1000
+ typeof summarizeHistory === 'function' &&
1001
+ ContextManager.shouldCompress(workingMessages, { ...safeContextOptions, model, providerType: apiType })) {
1002
+ const compacted = await ContextManager.compressHistory({
1003
+ messages: workingMessages,
1004
+ summarizeFn: summarizeHistory,
1005
+ options: { ...safeContextOptions, model, providerType: apiType }
1006
+ });
1007
+ if (compacted.compressed) {
1008
+ workingMessages = compacted.messages;
1009
+ } else {
1010
+ // A failed or unavailable summarizer cannot make progress during
1011
+ // this execution; retrying it before every agent step only adds calls.
1012
+ compressionUnavailable = true;
1013
+ }
1014
+ }
1015
+
1016
+ // 1. Optimización dinámica de presupuesto de contexto (Context Budget)
1017
+ let stepMessages = workingMessages;
1018
+ let contextDiagnostics = null;
1019
+ if (typeof prepareMessages === 'function') {
1020
+ const prepared = await prepareMessages(workingMessages, {
1021
+ stepIndex,
1022
+ model,
1023
+ apiType,
1024
+ isSynthesis: false
1025
+ });
1026
+ if (Array.isArray(prepared)) {
1027
+ stepMessages = prepared;
1028
+ } else if (prepared && Array.isArray(prepared.messages)) {
1029
+ stepMessages = prepared.messages;
1030
+ contextDiagnostics = prepared.diagnostics || null;
1031
+ }
1032
+ } else if (ContextManager && ContextManager.buildOptimizedContext) {
1033
+ try {
1034
+ const opt = ContextManager.buildOptimizedContext(workingMessages, {
1035
+ ...safeContextOptions,
1036
+ model,
1037
+ providerType: apiType
1038
+ });
1039
+ if (opt && opt.messages) {
1040
+ stepMessages = opt.messages;
1041
+ }
1042
+ } catch (e) {
1043
+ stepMessages = workingMessages;
1044
+ }
1045
+ }
1046
+ if (callbacks.onContextPrepared) callbacks.onContextPrepared(contextDiagnostics, stepIndex);
1047
+
1048
+ // 2. Definición de herramientas activas
1049
+ const activeToolDefs = enableTools
1050
+ ? (typeof resolveToolDefinitions === 'function'
1051
+ ? await resolveToolDefinitions({ stepIndex, workingMessages })
1052
+ : this.registry.getDefinitions(toolFilterOptions))
1053
+ : [];
1054
+
1055
+ let currentStepText = '';
1056
+ let currentStepReasoning = '';
1057
+ let stepToolCalls = null;
1058
+ let stepStats = null;
1059
+ let streamError = null;
1060
+
1061
+ try {
1062
+ const streamResult = await API.streamChatCompletion({
1063
+ apiUrl,
1064
+ apiType,
1065
+ apiKey,
1066
+ model,
1067
+ messages: stepMessages,
1068
+ temperature,
1069
+ reasoningEffort,
1070
+ reasoningTransport,
1071
+ tools: activeToolDefs,
1072
+ enableTools: activeToolDefs.length > 0,
1073
+ enableAgentJs: toolFilterOptions.enableAgentJs !== false,
1074
+ enableAgentWeb: toolFilterOptions.enableAgentWeb !== false,
1075
+ enableAgentSearch: toolFilterOptions.enableAgentSearch !== false,
1076
+ enableAgentChart: toolFilterOptions.enableAgentChart !== false,
1077
+ signal: combinedSignal,
1078
+ onBeforeRequest,
1079
+ onGenerationStatus: status => callbacks.onGenerationStatus?.(status),
1080
+
1081
+ onReasoningChunk: (chunk, accumulated) => {
1082
+ currentStepReasoning = accumulated;
1083
+ if (callbacks.onReasoningChunk) callbacks.onReasoningChunk(chunk, accumulated, stepIndex);
1084
+ },
1085
+ onLog: (logData) => {
1086
+ if (callbacks.onLog) callbacks.onLog(logData);
1087
+ },
1088
+ onChunk: (fullTextSoFar, delta, stats) => {
1089
+ currentStepText = fullTextSoFar;
1090
+ if (callbacks.onChunk) callbacks.onChunk(fullTextSoFar, delta, stats, stepIndex);
1091
+ },
1092
+ onDone: (finalText, stats, toolCalls, reasoning) => {
1093
+ currentStepText = finalText || currentStepText;
1094
+ currentStepReasoning = reasoning || currentStepReasoning;
1095
+ stepStats = stats;
1096
+ stepToolCalls = toolCalls;
1097
+ },
1098
+ onError: (error) => {
1099
+ streamError = error;
1100
+ if (callbacks.onError) callbacks.onError(error);
1101
+ }
1102
+ });
1103
+
1104
+ if (streamError) {
1105
+ if (combinedSignal.aborted) {
1106
+ status = isTimedOut ? 'timeout' : 'cancelled';
1107
+ executionError = new Error(isTimedOut ? 'Tiempo límite de ejecución agéntica excedido.' : 'Ejecución agéntica cancelada por el usuario.');
1108
+ } else {
1109
+ status = 'error';
1110
+ executionError = streamError;
1111
+ if (callbacks.onError) callbacks.onError(streamError);
1112
+ }
1113
+ break;
1114
+ }
1115
+
1116
+ if (streamResult) {
1117
+ currentStepText = streamResult.accumulatedText || currentStepText;
1118
+ currentStepReasoning = streamResult.accumulatedReasoning || currentStepReasoning;
1119
+ stepToolCalls = streamResult.toolCalls || stepToolCalls;
1120
+ stepStats = streamResult.stats || stepStats;
1121
+ }
1122
+
1123
+ lastStats = stepStats || lastStats;
1124
+ if (currentStepReasoning) {
1125
+ finalReasoningText += (finalReasoningText ? '\n' : '') + currentStepReasoning;
1126
+ }
1127
+
1128
+ // Caso A: Sin llamadas a herramientas -> Turno final
1129
+ if (!stepToolCalls || stepToolCalls.length === 0) {
1130
+ // If the model returned empty text after a tool turn, log it and move on
1131
+ if ((!currentStepText || currentStepText.trim() === '') && stepIndex > 0 && workingMessages.length > 0 && workingMessages[workingMessages.length - 1].role === 'tool') {
1132
+ if (callbacks.onLog) callbacks.onLog({ type: 'warn', text: 'Model ended the tool loop without producing a response.' });
1133
+ }
1134
+
1135
+ finalAccumulatedText = currentStepText;
1136
+ if (appendFinalMessage) {
1137
+ workingMessages.push({
1138
+ id: typeof createMessageId === 'function'
1139
+ ? createMessageId('final', { stepIndex })
1140
+ : `msg_turn_${stepIndex}_final`,
1141
+ role: 'assistant',
1142
+ content: currentStepText
1143
+ });
1144
+ }
1145
+ if (callbacks.onStepDone) {
1146
+ callbacks.onStepDone(stepIndex, {
1147
+ type: 'final_response',
1148
+ text: currentStepText,
1149
+ stats: lastStats
1150
+ });
1151
+ }
1152
+ status = 'completed';
1153
+ break;
1154
+ }
1155
+
1156
+ // Caso B: Llamadas a herramientas detectadas (soporte para llamadas individuales y en paralelo)
1157
+ const currentSignatures = stepToolCalls.map(tc => this.getToolCallFingerprint(tc));
1158
+ const allRepeated = currentSignatures.length > 0 && currentSignatures.every(sig => {
1159
+ return toolCallSignatures.filter(s => s === sig).length >= loopThreshold;
1160
+ });
1161
+
1162
+ // Detección y protección contra bucles infinitos
1163
+ if (allRepeated) {
1164
+ status = 'loop_detected';
1165
+ if (callbacks.onLoopDetected) {
1166
+ callbacks.onLoopDetected(stepToolCalls[0], loopThreshold + 1, stepIndex);
1167
+ }
1168
+ const loopWarning = '\n\n> ⚠️ *[Infinite Loop Protection]*: Tools were repeatedly invoked with identical parameters without progress. Halting agent turn loop.';
1169
+ currentStepText = (currentStepText || '') + loopWarning;
1170
+ finalAccumulatedText = currentStepText;
1171
+ if (appendFinalMessage) {
1172
+ workingMessages.push({
1173
+ id: typeof createMessageId === 'function'
1174
+ ? createMessageId('final', { stepIndex })
1175
+ : `msg_turn_${stepIndex}_final`,
1176
+ role: 'assistant',
1177
+ content: currentStepText
1178
+ });
1179
+ }
1180
+
1181
+ if (callbacks.onStepDone) {
1182
+ callbacks.onStepDone(stepIndex, {
1183
+ type: 'loop_detected',
1184
+ text: currentStepText,
1185
+ assistantMsg: {
1186
+ id: typeof createMessageId === 'function'
1187
+ ? createMessageId('final', { stepIndex })
1188
+ : `msg_turn_${stepIndex}_final`,
1189
+ role: 'assistant',
1190
+ content: currentStepText
1191
+ }
1192
+ });
1193
+ }
1194
+
1195
+ if (autoSynthesize && synthesizeOnLoop && !combinedSignal.aborted) {
1196
+ if (callbacks.onSynthesize) callbacks.onSynthesize(stepIndex);
1197
+ try {
1198
+ const synthRes = await API.streamChatCompletion({
1199
+ apiUrl,
1200
+ apiType,
1201
+ apiKey,
1202
+ model,
1203
+ messages: [
1204
+ ...workingMessages,
1205
+ { role: 'assistant', content: currentStepText }
1206
+ ],
1207
+ temperature,
1208
+ reasoningEffort,
1209
+ reasoningTransport,
1210
+ enableTools: false,
1211
+ signal: combinedSignal,
1212
+ onGenerationStatus: status => callbacks.onGenerationStatus?.(status),
1213
+ onChunk: (fullTextSoFar, delta, stats) => {
1214
+ finalAccumulatedText = fullTextSoFar;
1215
+ if (callbacks.onChunk) callbacks.onChunk(fullTextSoFar, delta, stats);
1216
+ },
1217
+ onDone: (finalText, stats) => {
1218
+ finalAccumulatedText = finalText || finalAccumulatedText;
1219
+ lastStats = stats || lastStats;
1220
+ }
1221
+ });
1222
+ } catch (e) {}
1223
+ }
1224
+ break;
1225
+ }
1226
+ for (const sig of currentSignatures) {
1227
+ toolCallSignatures.push(sig);
1228
+ }
1229
+
1230
+ const trimmedStepText = (currentStepText || '').trim();
1231
+ if (
1232
+ trimmedStepText.startsWith('<|') ||
1233
+ trimmedStepText.startsWith('<tool_call') ||
1234
+ trimmedStepText.startsWith('<function_call') ||
1235
+ trimmedStepText.startsWith('call:') ||
1236
+ trimmedStepText.startsWith('{"name"') ||
1237
+ trimmedStepText.startsWith('```json\n{"name"') ||
1238
+ trimmedStepText.startsWith('download_pdf(') ||
1239
+ trimmedStepText.startsWith('downloadpdf(') ||
1240
+ trimmedStepText.startsWith('fetch_web_page(') ||
1241
+ trimmedStepText.startsWith('fetchwebpage(') ||
1242
+ trimmedStepText.startsWith('search_web(') ||
1243
+ trimmedStepText.startsWith('searchweb(') ||
1244
+ trimmedStepText.startsWith('execute_javascript(') ||
1245
+ trimmedStepText.startsWith('executejs(')
1246
+ ) {
1247
+ currentStepText = '';
1248
+ }
1249
+
1250
+ const executedToolMessages = [];
1251
+ const stepExecResults = [];
1252
+
1253
+ for (let i = 0; i < stepToolCalls.length; i++) {
1254
+ if (combinedSignal.aborted) break;
1255
+ const call = stepToolCalls[i];
1256
+ const toolFnName = call.function?.name || '';
1257
+
1258
+ if (callbacks.onToolStart) {
1259
+ const toolInstance = this.registry.getTool(toolFnName);
1260
+ callbacks.onToolStart(call, toolInstance, stepIndex);
1261
+ }
1262
+
1263
+ const toolContext = {
1264
+ signal: combinedSignal,
1265
+ ...params
1266
+ };
1267
+ const execResult = typeof dispatchToolCall === 'function'
1268
+ ? await dispatchToolCall(call, toolContext)
1269
+ : await this.executeToolWithRetries(
1270
+ call,
1271
+ toolContext,
1272
+ maxRetries,
1273
+ (tc, attempt, total, err) => {
1274
+ if (callbacks.onRetry) {
1275
+ callbacks.onRetry(tc, attempt, total, err, stepIndex);
1276
+ }
1277
+ }
1278
+ );
1279
+ stepExecResults.push(execResult);
1280
+
1281
+ let toolResponseContent = '';
1282
+ let toolImages = null;
1283
+ if (execResult.success && execResult.result !== undefined) {
1284
+ const serialized = execResult.resultText !== undefined
1285
+ ? execResult.resultText
1286
+ : execResult.tool.serializeResultForModel(execResult.args, execResult.result, execResult.outcome);
1287
+ const serializedText = typeof serialized === 'string' ? serialized : JSON.stringify(serialized);
1288
+
1289
+ // Las imágenes RAG se preservan como metadatos del mensaje de herramienta.
1290
+ // ChatEngine las transforma en evidencia multimodal en la siguiente petición.
1291
+ const ragDataUrl = execResult.result?.dataUrl;
1292
+ const ragMimeType = execResult.result?.mimeType;
1293
+ if (ragDataUrl && ragMimeType) {
1294
+ toolResponseContent = serializedText;
1295
+ toolImages = [{
1296
+ dataUrl: ragDataUrl,
1297
+ imageRef: execResult.result.imageRef,
1298
+ documentTitle: execResult.result.documentTitle,
1299
+ page: execResult.result.page
1300
+ }];
1301
+ } else {
1302
+ toolResponseContent = serializedText;
1303
+ }
1304
+
1305
+ } else {
1306
+ toolResponseContent = JSON.stringify({
1307
+ success: false,
1308
+ error: execResult.error || 'Error desconocido ejecutando la herramienta.'
1309
+ });
1310
+ if (callbacks.onToolError) {
1311
+ callbacks.onToolError(call, execResult.error, stepIndex);
1312
+ }
1313
+ }
1314
+
1315
+ if (callbacks.onToolComplete) {
1316
+ callbacks.onToolComplete(call, execResult, toolResponseContent, stepIndex);
1317
+ }
1318
+
1319
+ toolExecutions.push({
1320
+ step: stepIndex,
1321
+ toolName: toolFnName || 'tool',
1322
+ args: execResult.args || call.function?.arguments,
1323
+ result: execResult.result,
1324
+ success: execResult.success,
1325
+ executionTimeMs: execResult.executionTimeMs || 0,
1326
+ error: execResult.error || null
1327
+ });
1328
+
1329
+ const toolMessage = {
1330
+ id: typeof createMessageId === 'function'
1331
+ ? createMessageId('tool', { stepIndex, toolCall: call, toolIndex: i })
1332
+ : `msg_turn_${stepIndex}_tool_${call.id || i}_${Date.now()}`,
1333
+ role: 'tool',
1334
+ tool_call_id: call.id || `call_${Date.now()}_${i}`,
1335
+ name: toolFnName || 'tool',
1336
+ content: toolResponseContent
1337
+ };
1338
+ if (toolImages) toolMessage.images = toolImages;
1339
+ executedToolMessages.push(toolMessage);
1340
+ }
1341
+
1342
+ // Actualizar historial asegurando el emparejamiento estricto assistant(tool_calls) <-> tool(results)
1343
+ const assistantMsg = {
1344
+ id: typeof createMessageId === 'function'
1345
+ ? createMessageId('assistant', { stepIndex })
1346
+ : `msg_turn_${stepIndex}_assistant`,
1347
+ role: 'assistant',
1348
+ content: currentStepText || null,
1349
+ tool_calls: stepToolCalls
1350
+ };
1351
+
1352
+ workingMessages.push(assistantMsg);
1353
+ for (const tMsg of executedToolMessages) {
1354
+ workingMessages.push(tMsg);
1355
+ }
1356
+
1357
+ if (callbacks.onStepDone) {
1358
+ callbacks.onStepDone(stepIndex, {
1359
+ type: 'tool_execution',
1360
+ toolCall: stepToolCalls[0],
1361
+ toolCalls: stepToolCalls,
1362
+ execResult: stepExecResults[0],
1363
+ execResults: stepExecResults,
1364
+ assistantMsg,
1365
+ toolMsg: executedToolMessages[0],
1366
+ toolMsgs: executedToolMessages
1367
+ });
1368
+ }
1369
+
1370
+ stepIndex++;
1371
+ } catch (err) {
1372
+ if (combinedSignal.aborted) {
1373
+ status = isTimedOut ? 'timeout' : 'cancelled';
1374
+ executionError = new Error(isTimedOut ? 'Tiempo límite de ejecución agéntica excedido.' : 'Ejecución agéntica cancelada por el usuario.');
1375
+ } else {
1376
+ status = 'error';
1377
+ executionError = err;
1378
+ if (callbacks.onError) callbacks.onError(err);
1379
+ }
1380
+ break;
1381
+ }
1382
+ }
1383
+
1384
+ // Si se alcanzó el límite máximo de iteraciones
1385
+ if (stepIndex >= maxSteps && status === 'completed') {
1386
+ status = 'max_steps';
1387
+ if (workingMessages.length > 0 && workingMessages[workingMessages.length - 1].role === 'tool' && autoSynthesize && !combinedSignal.aborted) {
1388
+ if (callbacks.onSynthesize) callbacks.onSynthesize(stepIndex);
1389
+ try {
1390
+ const hasRagTool = workingMessages.some(m => m.name === 'search_knowledge_base' || m.name === 'read_knowledge_chunk' || m.name === 'list_documents');
1391
+ const synthPrompt = hasRagTool
1392
+ ? 'Based on the information obtained by the previous tools, answer my initial query directly. If the requested information or data was not found in the consulted documents, clearly state that no data was found to answer the question, rather than summarizing everything or dumping consulted chunks.'
1393
+ : 'Please provide a complete, structured, and detailed final summary answering my query based on all the information obtained from the tools.';
1394
+
1395
+ let synthMessages = workingMessages;
1396
+ if (typeof prepareMessages === 'function') {
1397
+ const prepared = await prepareMessages(workingMessages, {
1398
+ stepIndex,
1399
+ model,
1400
+ apiType,
1401
+ isSynthesis: true
1402
+ });
1403
+ synthMessages = Array.isArray(prepared)
1404
+ ? prepared
1405
+ : (Array.isArray(prepared?.messages) ? prepared.messages : workingMessages);
1406
+ }
1407
+ synthMessages = [...synthMessages, { role: 'user', content: synthPrompt }];
1408
+ const synthRes = await API.streamChatCompletion({
1409
+ apiUrl,
1410
+ apiType,
1411
+ apiKey,
1412
+ model,
1413
+ messages: synthMessages,
1414
+ temperature,
1415
+ reasoningEffort,
1416
+ reasoningTransport,
1417
+ enableTools: false,
1418
+ toolChoice: 'none',
1419
+ signal: combinedSignal,
1420
+ onBeforeRequest,
1421
+ onGenerationStatus: status => callbacks.onGenerationStatus?.(status),
1422
+ onChunk: (fullTextSoFar, delta, stats) => {
1423
+ finalAccumulatedText = fullTextSoFar;
1424
+ if (callbacks.onChunk) callbacks.onChunk(fullTextSoFar, delta, stats);
1425
+ },
1426
+ onDone: (finalText, stats) => {
1427
+ finalAccumulatedText = finalText || finalAccumulatedText;
1428
+ lastStats = stats || lastStats;
1429
+ }
1430
+ });
1431
+ if (synthRes && synthRes.accumulatedText) {
1432
+ finalAccumulatedText = synthRes.accumulatedText;
1433
+ }
1434
+ } catch (e) {
1435
+ if (typeof console !== 'undefined' && console.warn) {
1436
+ console.warn('[ChatAgentCore] Error during auto-synthesis:', e);
1437
+ }
1438
+ if (callbacks && typeof callbacks.onLog === 'function') {
1439
+ callbacks.onLog({ type: 'warning', text: `[Auto-synthesis] Fallback activado tras error: ${e?.message || String(e)}` });
1440
+ }
1441
+ }
1442
+
1443
+ if (!finalAccumulatedText || finalAccumulatedText.trim() === '') {
1444
+ const hasRagTool = workingMessages.some(m => m.name === 'search_knowledge_base' || m.name === 'read_knowledge_chunk' || m.name === 'list_documents');
1445
+ if (hasRagTool) {
1446
+ finalAccumulatedText = 'No data was found in the consulted documents to answer the question.';
1447
+ } else {
1448
+ const toolContents = workingMessages
1449
+ .filter(m => m.role === 'tool' && m.content)
1450
+ .map(m => m.content)
1451
+ .filter(Boolean);
1452
+ if (toolContents.length > 0) {
1453
+ finalAccumulatedText = '### Summary of Consulted Information\n\n' + toolContents.join('\n\n---\n\n');
1454
+ }
1455
+ }
1456
+ }
1457
+ if (appendFinalMessage && finalAccumulatedText) {
1458
+ workingMessages.push({
1459
+ id: typeof createMessageId === 'function'
1460
+ ? createMessageId('final', { stepIndex })
1461
+ : `msg_turn_${stepIndex}_final`,
1462
+ role: 'assistant',
1463
+ content: finalAccumulatedText
1464
+ });
1465
+ }
1466
+ }
1467
+ }
1468
+
1469
+ if (timeoutTimer) {
1470
+ clearTimeout(timeoutTimer);
1471
+ }
1472
+ if (signal && onSignalAbort && typeof signal.removeEventListener === 'function') {
1473
+ signal.removeEventListener('abort', onSignalAbort);
1474
+ }
1475
+
1476
+ const endTime = typeof performance !== 'undefined' ? performance.now() : Date.now();
1477
+ const totalElapsedMs = Math.round(endTime - startTime);
1478
+
1479
+ if (callbacks.onDone) {
1480
+ callbacks.onDone(finalAccumulatedText, lastStats, stepIndex);
1481
+ }
1482
+
1483
+ return {
1484
+ success: status === 'completed' || status === 'max_steps' || status === 'loop_detected',
1485
+ status,
1486
+ finalText: finalAccumulatedText,
1487
+ reasoningText: finalReasoningText,
1488
+ stepsCount: stepIndex,
1489
+ maxStepsReached: status === 'max_steps',
1490
+ loopDetected: status === 'loop_detected',
1491
+ toolExecutions,
1492
+ history: workingMessages,
1493
+ stats: {
1494
+ totalTimeMs: totalElapsedMs,
1495
+ tokens: lastStats?.tokens || 0,
1496
+ tokensPerSec: lastStats?.tokensPerSec || 0,
1497
+ cachedTokens: lastStats?.cachedTokens || 0,
1498
+ cacheCreationTokens: lastStats?.cacheCreationTokens || 0,
1499
+ promptTokens: lastStats?.promptTokens || 0,
1500
+ completionTokens: lastStats?.completionTokens || 0,
1501
+ totalTokens: lastStats?.totalTokens || 0,
1502
+ reasoningTokens: lastStats?.reasoningTokens || 0,
1503
+ ttftSec: lastStats?.ttftSec || 0,
1504
+ totalSec: lastStats?.totalSec || ((totalElapsedMs / 1000).toFixed(2)),
1505
+ isEstimated: lastStats?.isEstimated ?? true
1506
+ },
1507
+ error: executionError
1508
+ };
1509
+ }
1510
+ }
1511
+
1512
+ /**
1513
+ * Orquestador Agéntico del Ciclo de Vida de Conversación (AgentCore).
1514
+ * Mantiene compatibilidad total con la API previa extendiendo AgentRuntime.
1515
+ */
1516
+ class AgentCore extends AgentRuntime {
1517
+ constructor(options = {}) {
1518
+ super(options);
1519
+ this.maxTurns = this.maxSteps;
1520
+ }
1521
+
1522
+ /**
1523
+ * Alias compatible con la firma previa runConversationLoop.
1524
+ */
1525
+ async runConversationLoop(params = {}) {
1526
+ return this.execute(params);
1527
+ }
1528
+ }
1529
+
1530
+ const globalRegistry = new ToolRegistry();
1531
+ const globalExecutor = new ToolExecutor(globalRegistry);
1532
+ const globalRuntime = new AgentRuntime({ registry: globalRegistry, executor: globalExecutor });
1533
+ const globalAgent = new AgentCore({ registry: globalRegistry, executor: globalExecutor });
1534
+
1535
+ return {
1536
+ TOOL_CONTRACT_VERSION,
1537
+ ToolOutcome,
1538
+ validateToolContract,
1539
+ Tool,
1540
+ BaseToolProvider,
1541
+ BuiltinToolProvider,
1542
+ ToolRegistry,
1543
+ ToolExecutor,
1544
+ AgentRuntime,
1545
+ AgentCore,
1546
+ registry: globalRegistry,
1547
+ executor: globalExecutor,
1548
+ runtime: globalRuntime,
1549
+ agent: globalAgent,
1550
+ dispatchToolCall: (toolCall, options) => globalExecutor.dispatchToolCall(toolCall, options),
1551
+ executeToolCall: (toolCall, context) => globalExecutor.executeToolCall(toolCall, context)
1552
+ };
1553
+ });