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,566 @@
1
+ /**
2
+ * Motor Agéntico de Mensajería y Streaming (ChatEngine) para ZeroChat.
3
+ * Responsable de:
4
+ * - Construcción y anclaje de mensajes efectivos (System Prompt, Context-Caching, RAG context).
5
+ * - Orquestación del bucle agéntico multi-turno (turnos asistentes, llamadas a herramientas y síntesis forzada).
6
+ * - Streaming de tokens, reasoning/thinking chunks y cálculo de métricas en tiempo real.
7
+ * - Prevención de bucles infinitos por llamadas idénticas consecutivas.
8
+ */
9
+
10
+ (function (root, factory) {
11
+ if (typeof exports === 'object' && typeof module !== 'undefined') {
12
+ module.exports = factory(require('./message-turns.js'));
13
+ } else {
14
+ root.ChatEngine = factory(root.ChatMessageTurns);
15
+ }
16
+ })(typeof self !== 'undefined' ? self : this, function (MessageTurns) {
17
+ 'use strict';
18
+
19
+ function getAPI() {
20
+ return (typeof window !== 'undefined' && window.ChatAPI)
21
+ ? window.ChatAPI
22
+ : (typeof require !== 'undefined' ? (() => { try { return require('./api.js'); } catch (e) { return {}; } })() : {});
23
+ }
24
+
25
+ function getAgentCore() {
26
+ return (typeof window !== 'undefined' && window.ChatAgentCore)
27
+ ? window.ChatAgentCore
28
+ : (typeof require !== 'undefined' ? (() => { try { return require('./agent-core.js'); } catch (e) { return {}; } })() : {});
29
+ }
30
+
31
+ function getMarkdown() {
32
+ return (typeof window !== 'undefined' && window.ChatMarkdown)
33
+ ? window.ChatMarkdown
34
+ : (typeof require !== 'undefined' ? (() => { try { return require('./markdown.js'); } catch (e) { return {}; } })() : {});
35
+ }
36
+
37
+ function getI18n() {
38
+ return (typeof window !== 'undefined' && window.ChatI18n)
39
+ ? window.ChatI18n
40
+ : (typeof require !== 'undefined' ? (() => { try { return require('./i18n.js'); } catch (e) { return {}; } })() : {});
41
+ }
42
+
43
+ function getContextManager() {
44
+ return (typeof window !== 'undefined' && window.ChatContextManager)
45
+ ? window.ChatContextManager
46
+ : (typeof require !== 'undefined' ? (() => { try { return require('./context-manager.js'); } catch (e) { return {}; } })() : {});
47
+ }
48
+
49
+
50
+ function serializeContent(content) {
51
+ if (typeof content === 'string') return content;
52
+ if (content === undefined) return '';
53
+ if (typeof content === 'object') return JSON.stringify(content);
54
+ return String(content);
55
+ }
56
+
57
+ /** Genera una referencia de fecha inicial, sin hora y coherente con la zona local (siempre en inglés para el modelo). */
58
+ function getConversationDateAnchor(lang = 'en', startedAt = Date.now()) {
59
+ let date = new Date(startedAt);
60
+ if (Number.isNaN(date.getTime())) date = new Date();
61
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
62
+ const parts = new Intl.DateTimeFormat('en-US', {
63
+ timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit'
64
+ }).formatToParts(date);
65
+ const value = type => parts.find(part => part.type === type).value;
66
+ const isoDate = `${value('year')}-${value('month')}-${value('day')}`;
67
+ return `[Conversation start date: ${isoDate}, Timezone: ${tz}.]`;
68
+ }
69
+
70
+ /** Guarda el ancla una sola vez; el metadato no se transmite en el payload. */
71
+ function ensureConversationDate(chatHistory = [], lang = 'es', startedAt = Date.now()) {
72
+ const root = chatHistory.find(m => m && m.role === 'system' && !m._isSummaryBlock)
73
+ || chatHistory.find(m => m && m.role);
74
+ if (!root) return getConversationDateAnchor(lang, startedAt);
75
+ if (typeof root.contextDateAnchor !== 'string' || !root.contextDateAnchor.trim()) {
76
+ root.contextDateAnchor = getConversationDateAnchor(lang, startedAt);
77
+ }
78
+ return root.contextDateAnchor;
79
+ }
80
+
81
+ /**
82
+ * Genera la guía de herramientas en texto plano / XML para modelos que no soportan Function Calling nativo en JSON.
83
+ * @param {Object} [appConfig={}] - Configuración activa de herramientas.
84
+ * @param {string} [lang='es'] - Idioma ('es' o 'en').
85
+ * @returns {string} - Guía formateada para el System Prompt.
86
+ */
87
+ function getToolsSystemPromptGuide(appConfig = {}, lang = 'es') {
88
+ const AgentCore = getAgentCore();
89
+ if (AgentCore && AgentCore.registry && typeof AgentCore.registry.getActivePromptGuide === 'function') {
90
+ return AgentCore.registry.getActivePromptGuide(appConfig, lang);
91
+ }
92
+ return '';
93
+ }
94
+
95
+ function getConfiguredSystemPrompt(appConfig = {}) {
96
+ return [appConfig.systemPrompt, appConfig.systemDataPrompt]
97
+ .map(value => String(value || '').trim())
98
+ .filter(Boolean)
99
+ .join('\n\n');
100
+ }
101
+
102
+ function getUIConversation() {
103
+ if (typeof window !== 'undefined' && window.ChatUIConversation) return window.ChatUIConversation;
104
+ return typeof require !== 'undefined' ? require('./ui-conversation.js') : null;
105
+ }
106
+
107
+ /** Compatibility facade for the shared response renderer. */
108
+ function injectStreamingCursor(html) {
109
+ return getUIConversation().injectStreamingCursor(html);
110
+ }
111
+
112
+ /**
113
+ * Extrae el identificador base de un mensaje eliminando sufijos de turnos internos o finalización.
114
+ * @param {string} id - Identificador del mensaje (ej: 'asst_123_turn_0_assistant', 'asst_123_final').
115
+ * @returns {string} - Identificador base (ej: 'asst_123').
116
+ */
117
+ const extractBaseId = MessageTurns.extractBaseId;
118
+
119
+ /**
120
+ * Elimina completamente un turno del historial de chat, asegurando que:
121
+ * 1. Se eliminen todos los mensajes del turno (asistente, herramientas y síntesis final).
122
+ * 2. Se eliminen todas las respuestas de herramientas asociadas a las llamadas de ese turno.
123
+ * 3. No queden mensajes 'tool' huérfanos sin llamada previa en el historial.
124
+ *
125
+ * @param {Array} chatHistory - Historial actual de mensajes.
126
+ * @param {Object} options - Parámetros de identificación del turno a eliminar.
127
+ * @param {string} [options.msgId] - ID principal o data-msg-id del contenedor.
128
+ * @param {string} [options.baseId] - Prefijo base del turno (ej: 'msg_ast_123').
129
+ * @param {Array<string>|Set<string>} [options.explicitIds] - Lista de IDs exactos pertenecientes al turno.
130
+ * @returns {Array} - Nuevo historial filtrado y saneado sin turnos ni respuestas huérfanas.
131
+ */
132
+ function removeTurnFromHistory(chatHistory = [], options = {}) {
133
+ const msgId = options.msgId || '';
134
+ const baseId = options.baseId || extractBaseId(msgId);
135
+ return MessageTurns.removeSelectedTurn(chatHistory, { ...options, msgId, baseId });
136
+ }
137
+
138
+ /**
139
+ * Construye el array de mensajes normalizado y enriquecido para la API de inferencia.
140
+ * @param {Array} chatHistory - Historial de mensajes de la conversación.
141
+ * @param {Object} appConfig - Configuración activa de la aplicación.
142
+ * @param {Object} [options={}] - Opciones de contexto (ej: currentRagSystemContext, forceSystemPromptGuide).
143
+ * @returns {Array} - Array de mensajes listo para streamChatCompletion.
144
+ */
145
+ function buildEffectiveMessages(chatHistory = [], appConfig = {}, options = {}) {
146
+ const rawMessages = (chatHistory || []).filter(m => m && m.role);
147
+ const messages = [];
148
+
149
+ rawMessages.forEach(m => {
150
+ if (m.role === 'user') {
151
+ if (m.images && Array.isArray(m.images) && m.images.length > 0) {
152
+ const contentParts = [];
153
+ if (m.content) {
154
+ contentParts.push({ type: 'text', text: m.content });
155
+ }
156
+ m.images.forEach(img => {
157
+ if (img && img.dataUrl) {
158
+ contentParts.push({
159
+ type: 'image_url',
160
+ image_url: {
161
+ url: img.dataUrl
162
+ }
163
+ });
164
+ }
165
+ });
166
+ messages.push({ role: 'user', content: contentParts });
167
+ } else {
168
+ messages.push({ role: 'user', content: m.content || '' });
169
+ }
170
+ } else if (m.role === 'assistant') {
171
+ const item = { role: 'assistant', content: m.content !== undefined ? m.content : '' };
172
+ if (m.tool_calls && Array.isArray(m.tool_calls) && m.tool_calls.length > 0) {
173
+ item.tool_calls = m.tool_calls;
174
+ }
175
+ messages.push(item);
176
+ } else if (m.role === 'tool') {
177
+ const toolCallId = m.tool_call_id || `call_${Date.now()}`;
178
+ const toolName = m.name || 'tool';
179
+ const toolContent = serializeContent(m.content);
180
+
181
+ // Validar que el mensaje previo sea un assistant con el tool_call correspondiente
182
+ const prevMsg = messages.length > 0 ? messages[messages.length - 1] : null;
183
+ const hasMatchingToolCall = prevMsg && prevMsg.role === 'assistant' && Array.isArray(prevMsg.tool_calls) &&
184
+ prevMsg.tool_calls.some(tc => tc.id === toolCallId || (tc.function && tc.function.name === toolName));
185
+
186
+ if (!hasMatchingToolCall) {
187
+ messages.push({
188
+ role: 'assistant',
189
+ content: null,
190
+ tool_calls: [{
191
+ id: toolCallId,
192
+ type: 'function',
193
+ function: {
194
+ name: toolName,
195
+ arguments: '{}'
196
+ }
197
+ }]
198
+ });
199
+ }
200
+
201
+ messages.push({
202
+ role: 'tool',
203
+ tool_call_id: toolCallId,
204
+ name: toolName,
205
+ content: toolContent
206
+ });
207
+
208
+ // La imagen recuperada por RAG se entrega como evidencia visual en el
209
+ // siguiente turno de inferencia, sin convertirla en un mensaje visible
210
+ // ni en texto/base64 dentro del resultado de la herramienta.
211
+ const image = Array.isArray(m.images) ? m.images.find(item => item?.dataUrl) : null;
212
+ if (toolName === 'read_knowledge_image' && image) {
213
+ const provenance = [image.imageRef, image.documentTitle, image.page ? `page ${image.page}` : '']
214
+ .filter(Boolean).join(' · ');
215
+ messages.push({
216
+ role: 'user',
217
+ content: [
218
+ { type: 'text', text: `Visual evidence retrieved by read_knowledge_image${provenance ? `: ${provenance}` : '.'}` },
219
+ { type: 'image_url', image_url: { url: image.dataUrl } }
220
+ ]
221
+ });
222
+ }
223
+ } else if (m.role === 'system') {
224
+ messages.push({
225
+ role: 'system',
226
+ content: m.content || '',
227
+ ...(m._isSummaryBlock ? { _isSummaryBlock: true } : {})
228
+ });
229
+ }
230
+ });
231
+
232
+ // 1. Instrucciones base del sistema (máxima estabilidad de prefijo)
233
+ const baseSystemPrompt = getConfiguredSystemPrompt(appConfig);
234
+
235
+ const isToolsEnabled = options.enableTools !== undefined
236
+ ? Boolean(options.enableTools)
237
+ : Boolean(appConfig.enabledTools && Object.values(appConfig.enabledTools).some(value => value !== false));
238
+
239
+ // Consultar si el modelo soporta llamadas a herramientas nativas
240
+ const API = getAPI();
241
+ let isNativeToolsSupported = true;
242
+ if (API && API.getProviderCapabilities) {
243
+ const caps = API.getProviderCapabilities(appConfig.apiUrl, appConfig.apiType, appConfig.model);
244
+ isNativeToolsSupported = caps ? (caps.tools !== false) : true;
245
+ }
246
+
247
+ // 2. Instrucción de flujo para herramientas (estable)
248
+ const lang = appConfig.language || 'es';
249
+ let toolsGuide = '';
250
+ if (isToolsEnabled) {
251
+ if (!isNativeToolsSupported || options.forceSystemPromptGuide) {
252
+ toolsGuide = getToolsSystemPromptGuide(appConfig, lang);
253
+ } else {
254
+ const isCheckpointActive = !!(appConfig?.enabledTools?.agent_checkpoint);
255
+ const checkpointGuidance = isCheckpointActive
256
+ ? '\n*Agent checkpoint:* When gathering information from multiple searches or documents, or before concluding, invoke "agent_checkpoint" to consolidate facts and record the current plan.'
257
+ : '';
258
+ toolsGuide = `*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.${checkpointGuidance}`;
259
+ }
260
+ }
261
+
262
+ // 3. Directiva proactiva de Base de Conocimiento activa (estable por rama)
263
+ const activeBranchIds = Array.isArray(options.activeRagBranchIds)
264
+ ? options.activeRagBranchIds
265
+ : (options.activeRagBranchId ? [options.activeRagBranchId] : (appConfig.activeRagBranchIds || (appConfig.activeRagBranchId ? [appConfig.activeRagBranchId] : [])));
266
+ const activeBranchId = options.activeRagBranchId || (Array.isArray(activeBranchIds) ? activeBranchIds[0] : '') || (appConfig.activeRagBranchId || '');
267
+
268
+ if (activeBranchId || (Array.isArray(activeBranchIds) && activeBranchIds.length > 0)) {
269
+ const ragInstruction = `*Knowledge Base active:* Follow the document-consultation protocol above. Use 'list_documents' only when you explicitly need a complete inventory.`;
270
+ toolsGuide = toolsGuide ? `${toolsGuide}\n\n${ragInstruction}` : ragInstruction;
271
+ }
272
+
273
+ // Ensamblar bloque estable
274
+ let fullSystemPrompt = [baseSystemPrompt, toolsGuide].filter(Boolean).join('\n\n');
275
+
276
+ // 4. Inyección de contexto dinámico al final para preservar el prefijo en caché:
277
+ // a) Contexto RAG recuperado para esta consulta
278
+ const ragContext = options.currentRagSystemContext || appConfig.currentRagSystemContext || '';
279
+ if (ragContext) {
280
+ fullSystemPrompt = fullSystemPrompt ? `${fullSystemPrompt}\n\n${ragContext}` : ragContext;
281
+ }
282
+
283
+ // b) Fecha inicial persistida de la conversación (siempre activa)
284
+ const dateAnchor = ensureConversationDate(chatHistory, lang);
285
+ fullSystemPrompt = fullSystemPrompt ? `${fullSystemPrompt}\n\n${dateAnchor}` : dateAnchor;
286
+
287
+ if (messages.length > 0 && messages[0].role === 'system' && !messages[0]._isSummaryBlock) {
288
+ if (fullSystemPrompt) {
289
+ messages[0].content = fullSystemPrompt;
290
+ } else {
291
+ messages.shift();
292
+ }
293
+ } else if (fullSystemPrompt) {
294
+ messages.unshift({
295
+ role: 'system',
296
+ content: fullSystemPrompt
297
+ });
298
+ }
299
+
300
+ // Asegurar que la conversación comience con un turno de usuario válido tras el mensaje del sistema
301
+ const firstNonSysIdx = messages.findIndex(m => m.role !== 'system');
302
+ if (firstNonSysIdx !== -1 && messages[firstNonSysIdx].role === 'assistant') {
303
+ messages.splice(firstNonSysIdx, 0, {
304
+ role: 'user',
305
+ content: 'Continue'
306
+ });
307
+ }
308
+
309
+ // Optimización dinámica de contexto, presupuesto de tokens y ventana deslizante
310
+ const ContextManager = getContextManager();
311
+ if (ContextManager && ContextManager.buildOptimizedContext) {
312
+ const optimization = ContextManager.buildOptimizedContext(messages, {
313
+ model: appConfig.model,
314
+ providerType: appConfig.apiType,
315
+ totalContextLimit: appConfig.modelContextLimit || appConfig.contextLimitOverride,
316
+ ...options
317
+ });
318
+ options.onContextPrepared?.(optimization.diagnostics || null);
319
+ return optimization.messages;
320
+ }
321
+
322
+ return messages;
323
+ }
324
+
325
+ /**
326
+ * Ejecuta el bucle agéntico multi-turno de inferencia, streaming y herramientas.
327
+ * @param {Object} params - Parámetros de ejecución.
328
+ * @returns {Promise<{ success: boolean, finalAssistantText: string, accumulatedMarkdown: string, stats: Object, cancelled?: boolean, error?: any }>}
329
+ */
330
+ async function executeWithAgentRuntime(params = {}) {
331
+ const {
332
+ chatHistory = [],
333
+ appConfig = {},
334
+ assistantMsgId = `asst_${Date.now()}`,
335
+ activeRagBranchId = '',
336
+ activeRagBranchIds = [],
337
+ currentRagSystemContext = '',
338
+ container,
339
+ onTurnStart,
340
+ onChunk,
341
+ onReasoningChunk,
342
+ onLog,
343
+ onStats,
344
+ onBeforeRequest,
345
+ onGenerationStatus,
346
+ onToolCallStart,
347
+ onToolCallEnd,
348
+ scrollToBottom,
349
+ attachListeners
350
+ } = params;
351
+ const AgentCore = getAgentCore();
352
+ const Markdown = getMarkdown();
353
+ const runtime = AgentCore?.runtime;
354
+ if (!runtime || !AgentCore?.registry || !AgentCore?.dispatchToolCall) {
355
+ return { success: false, error: new Error('El runtime agéntico no está disponible.') };
356
+ }
357
+
358
+ const UIConversation = getUIConversation();
359
+ const attachEvts = attachListeners || Markdown.attachCopyCodeListeners || (() => {});
360
+ const scrollFn = scrollToBottom || (() => {});
361
+ let lastContextDiagnostics = null;
362
+ const resolvedBranchIds = Array.isArray(activeRagBranchIds) && activeRagBranchIds.length > 0
363
+ ? activeRagBranchIds
364
+ : (activeRagBranchId ? [activeRagBranchId] : (appConfig.activeRagBranchIds || (appConfig.activeRagBranchId ? [appConfig.activeRagBranchId] : [])));
365
+ const resolvedBranchId = activeRagBranchId || resolvedBranchIds[0] || appConfig.activeRagBranchId || '';
366
+ const turnBlocks = new Map();
367
+ let synthesisBlock = null;
368
+ const turnMarkdown = new Map();
369
+
370
+ const getTurnMarkdown = turnIndex => {
371
+ if (!turnMarkdown.has(turnIndex)) {
372
+ turnMarkdown.set(turnIndex, { text: '', toolBlocks: [] });
373
+ }
374
+ return turnMarkdown.get(turnIndex);
375
+ };
376
+
377
+ const result = await runtime.execute({
378
+ apiUrl: params.apiUrl || appConfig.apiUrl,
379
+ apiType: params.apiType || appConfig.apiType,
380
+ apiKey: params.apiKey || '',
381
+ model: params.model || appConfig.model,
382
+ temperature: params.temperature !== undefined ? params.temperature : appConfig.temperature,
383
+ reasoningEffort: params.reasoningEffort || appConfig.reasoningEffort || 'medium',
384
+ reasoningTransport: params.reasoningTransport || appConfig.reasoningTransport || 'auto',
385
+ messages: chatHistory,
386
+ signal: params.signal,
387
+ maxSteps: params.maxAgentTurns || appConfig.maxAgentTurns || 15,
388
+ maxRetries: 0,
389
+ autoSynthesize: true,
390
+ synthesizeOnLoop: false,
391
+ appendFinalMessage: true,
392
+ isCheckpointEnabled: Boolean(appConfig.enabledTools?.agent_checkpoint),
393
+ contextOptions: {
394
+ totalContextLimit: appConfig.modelContextLimit || appConfig.contextLimitOverride
395
+ },
396
+ summarizeHistory: async ({ systemPrompt, messages }) => {
397
+ const API = getAPI();
398
+ const response = await API.streamChatCompletion({
399
+ apiUrl: params.apiUrl || appConfig.apiUrl,
400
+ apiType: params.apiType || appConfig.apiType,
401
+ apiKey: params.apiKey || '',
402
+ model: params.model || appConfig.model,
403
+ messages: [
404
+ { role: 'system', content: systemPrompt },
405
+ ...messages,
406
+ { role: 'user', content: 'Generate the replacement checkpoint now.' }
407
+ ],
408
+ temperature: 0,
409
+ reasoningEffort: 'none',
410
+ enableTools: false,
411
+ toolChoice: 'none',
412
+ signal: params.signal,
413
+ onBeforeRequest
414
+ });
415
+ return response?.accumulatedText || '';
416
+ },
417
+ onBeforeRequest,
418
+ onGenerationStatus,
419
+ createMessageId: (kind, info) => {
420
+ if (kind === 'final') return `${assistantMsgId}_final`;
421
+ if (kind === 'assistant') return `${assistantMsgId}_turn_${info.stepIndex}_assistant`;
422
+ return `${assistantMsgId}_turn_${info.stepIndex}_tool_${info.toolCall?.id || info.toolIndex}_${Date.now()}`;
423
+ },
424
+ prepareMessages: (messages, options) => ({
425
+ messages: buildEffectiveMessages(messages, appConfig, {
426
+ currentRagSystemContext,
427
+ activeRagBranchId: resolvedBranchId,
428
+ activeRagBranchIds: resolvedBranchIds,
429
+ forceSystemPromptGuide: Boolean(options.isSynthesis),
430
+ onContextPrepared: diagnostics => { lastContextDiagnostics = diagnostics; }
431
+ }),
432
+ diagnostics: lastContextDiagnostics
433
+ }),
434
+ resolveToolDefinitions: () => AgentCore.registry.getActiveDefinitions({
435
+ ...appConfig,
436
+ activeRagBranchId: resolvedBranchId,
437
+ activeRagBranchIds: resolvedBranchIds
438
+ }),
439
+ dispatchToolCall: async (toolCall, context) => AgentCore.dispatchToolCall(toolCall, {
440
+ container,
441
+ onLog,
442
+ attachListeners: attachEvts,
443
+ scrollToBottom: scrollFn,
444
+ language: appConfig.language || 'es',
445
+ activeRagBranchId: resolvedBranchId,
446
+ activeRagBranchIds: resolvedBranchIds,
447
+ ...context
448
+ }),
449
+ callbacks: {
450
+ onGenerationStatus: status => onGenerationStatus?.(status),
451
+ onStepStart: turnIndex => {
452
+ if (container && typeof document !== 'undefined') {
453
+ if (turnIndex === 0) container.innerHTML = '';
454
+ const block = UIConversation.createAssistantBlock(container);
455
+ turnBlocks.set(turnIndex, block);
456
+ if (typeof onTurnStart === 'function') onTurnStart({ turnIndex, turnBlock: block });
457
+ }
458
+ },
459
+ onChunk: (text, delta, stats, turnIndex) => {
460
+ const block = turnBlocks.get(turnIndex) || synthesisBlock;
461
+ if (block) {
462
+ UIConversation.renderAssistantBlock(block, text, { streaming: true, attachListeners: attachEvts });
463
+ }
464
+ if (stats && typeof onStats === 'function') onStats(stats);
465
+ if (typeof onChunk === 'function') onChunk({ turnIndex, fullText: text, delta, stats });
466
+ scrollFn();
467
+ },
468
+ onReasoningChunk: (chunk, _accumulated, turnIndex) => {
469
+ if (typeof onReasoningChunk === 'function') onReasoningChunk(chunk, turnIndex);
470
+ },
471
+ onLog: logData => {
472
+ if (typeof onLog === 'function' && logData?.type !== 'thinking') onLog(logData.type, logData.text);
473
+ },
474
+ onToolStart: (toolCall, _tool, turnIndex) => {
475
+ const toolName = toolCall?.function?.name || _tool?.name || 'tool';
476
+ const i18n = (typeof window !== 'undefined' && window.ChatI18n) || null;
477
+ const statusText = i18n?.t ? i18n.t('generation_status_tool', { name: toolName }) : `Ejecutando ${toolName}...`;
478
+ onGenerationStatus?.({ phase: 'tool', text: statusText });
479
+ if (typeof onToolCallStart === 'function') onToolCallStart({ turnIndex, toolCall });
480
+ },
481
+ onToolComplete: (toolCall, execution, _content, turnIndex) => {
482
+ if (execution?.markdownBlock) {
483
+ getTurnMarkdown(turnIndex).toolBlocks.push(execution.markdownBlock);
484
+ }
485
+ if (typeof onToolCallEnd === 'function') onToolCallEnd({ turnIndex, toolCall, result: execution });
486
+ },
487
+ onStepDone: (turnIndex, step) => {
488
+ const block = turnBlocks.get(turnIndex);
489
+ if (step.type === 'tool_execution') {
490
+ getTurnMarkdown(turnIndex).text = step.assistantMsg?.content || '';
491
+ } else if (step.type === 'loop_detected') {
492
+ getTurnMarkdown(turnIndex).text = step.text || step.assistantMsg?.content || '';
493
+ }
494
+ if (!block) return;
495
+ const text = (step.type === 'final_response' || step.type === 'loop_detected') ? step.text : step.assistantMsg?.content;
496
+ if (text) {
497
+ UIConversation.renderAssistantBlock(block, text, { attachListeners: attachEvts });
498
+ } else if (typeof block.remove === 'function') {
499
+ block.remove();
500
+ turnBlocks.delete(turnIndex);
501
+ }
502
+ },
503
+ onLoopDetected: () => {
504
+ const i18n = (typeof window !== 'undefined' && window.ChatI18n) || null;
505
+ const statusText = i18n?.t ? i18n.t('generation_status_loop_detected') : 'Parada ordenada: bucle infinito detectado';
506
+ onGenerationStatus?.({ phase: 'error', text: statusText });
507
+ if (typeof onLog === 'function') {
508
+ onLog('error', '[Protección Bucle Infinito]: Herramientas invocadas repetidamente con los mismos argumentos. Interrumpiendo ciclo agéntico.');
509
+ }
510
+ },
511
+ onSynthesize: () => {
512
+ if (container && typeof document !== 'undefined') {
513
+ synthesisBlock = UIConversation.createAssistantBlock(container);
514
+ }
515
+ },
516
+ onDone: finalText => {
517
+ const block = synthesisBlock || turnBlocks.get(Math.max(...turnBlocks.keys(), 0));
518
+ if (block && finalText) {
519
+ UIConversation.renderAssistantBlock(block, finalText, { attachListeners: attachEvts });
520
+ }
521
+ }
522
+ }
523
+ });
524
+
525
+ if (Array.isArray(result.history)) {
526
+ chatHistory.length = 0;
527
+ chatHistory.push(...result.history);
528
+ }
529
+ if (result.cancelled || result.status === 'cancelled') return { success: false, cancelled: true };
530
+ if (result.error) return { success: false, error: result.error };
531
+ const turnTexts = [...turnMarkdown.entries()]
532
+ .sort(([left], [right]) => left - right)
533
+ .flatMap(([, turn]) => [turn.text, ...turn.toolBlocks])
534
+ .filter(Boolean);
535
+ const hasFinalInTurn = result.finalText && turnTexts.some(txt => txt && txt.includes(result.finalText));
536
+ const accumulatedMarkdown = turnTexts
537
+ .concat(hasFinalInTurn ? [] : (result.finalText ? [result.finalText] : []))
538
+ .filter(Boolean)
539
+ .join('\n\n');
540
+ return {
541
+ success: result.success,
542
+ loopDetected: result.loopDetected,
543
+ finalAssistantText: result.finalText || '',
544
+ accumulatedMarkdown,
545
+ stats: result.stats,
546
+ contextDiagnostics: lastContextDiagnostics,
547
+ chatHistory: result.history
548
+ };
549
+ }
550
+
551
+ async function executeAgentTurnLoop(params = {}) {
552
+ return executeWithAgentRuntime(params);
553
+ }
554
+
555
+ return {
556
+ getConversationDateAnchor,
557
+ ensureConversationDate,
558
+ getConfiguredSystemPrompt,
559
+ getToolsSystemPromptGuide,
560
+ injectStreamingCursor,
561
+ buildEffectiveMessages,
562
+ executeAgentTurnLoop,
563
+ extractBaseId,
564
+ removeTurnFromHistory
565
+ };
566
+ });