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.
- zerochat-7.3.0.dist-info/METADATA +70 -0
- zerochat-7.3.0.dist-info/RECORD +130 -0
- zerochat-7.3.0.dist-info/WHEEL +5 -0
- zerochat-7.3.0.dist-info/entry_points.txt +2 -0
- zerochat-7.3.0.dist-info/licenses/LICENSE +34 -0
- zerochat-7.3.0.dist-info/top_level.txt +2 -0
- zerochat.py +2004 -0
- zerochat_runtime/__init__.py +1 -0
- zerochat_runtime/assets/css/base.css +199 -0
- zerochat_runtime/assets/css/components/composer.css +1258 -0
- zerochat_runtime/assets/css/components/debug.css +777 -0
- zerochat_runtime/assets/css/components/header.css +97 -0
- zerochat_runtime/assets/css/components/icons.css +39 -0
- zerochat_runtime/assets/css/components/markdown.css +685 -0
- zerochat_runtime/assets/css/components/messages.css +473 -0
- zerochat_runtime/assets/css/components/modals.css +1319 -0
- zerochat_runtime/assets/css/components/sidebar.css +622 -0
- zerochat_runtime/assets/css/components/tools.css +1223 -0
- zerochat_runtime/assets/css/layout.css +171 -0
- zerochat_runtime/assets/css/print.css +120 -0
- zerochat_runtime/assets/css/styles.css +18 -0
- zerochat_runtime/assets/css/theme-overrides.css +555 -0
- zerochat_runtime/assets/css/tokens.css +165 -0
- zerochat_runtime/assets/help/architecture.html +247 -0
- zerochat_runtime/assets/help/composer.html +134 -0
- zerochat_runtime/assets/help/conversations.html +105 -0
- zerochat_runtime/assets/help/debug.html +108 -0
- zerochat_runtime/assets/help/en/architecture.html +247 -0
- zerochat_runtime/assets/help/en/composer.html +134 -0
- zerochat_runtime/assets/help/en/conversations.html +104 -0
- zerochat_runtime/assets/help/en/debug.html +105 -0
- zerochat_runtime/assets/help/en/gemini-free.html +39 -0
- zerochat_runtime/assets/help/en/index.html +191 -0
- zerochat_runtime/assets/help/en/learning.html +71 -0
- zerochat_runtime/assets/help/en/mcp.html +143 -0
- zerochat_runtime/assets/help/en/openrouter-free.html +39 -0
- zerochat_runtime/assets/help/en/profiles.html +173 -0
- zerochat_runtime/assets/help/en/rag.html +130 -0
- zerochat_runtime/assets/help/en/reasoning-telemetry.html +108 -0
- zerochat_runtime/assets/help/en/tools-agent.html +133 -0
- zerochat_runtime/assets/help/en/webllm.html +355 -0
- zerochat_runtime/assets/help/gemini-free.html +39 -0
- zerochat_runtime/assets/help/help.css +604 -0
- zerochat_runtime/assets/help/help.js +51 -0
- zerochat_runtime/assets/help/index.html +194 -0
- zerochat_runtime/assets/help/learning.html +71 -0
- zerochat_runtime/assets/help/mcp.html +143 -0
- zerochat_runtime/assets/help/openrouter-free.html +39 -0
- zerochat_runtime/assets/help/profiles.html +174 -0
- zerochat_runtime/assets/help/rag.html +130 -0
- zerochat_runtime/assets/help/reasoning-telemetry.html +111 -0
- zerochat_runtime/assets/help/tools-agent.html +133 -0
- zerochat_runtime/assets/help/webllm.html +359 -0
- zerochat_runtime/assets/js/agent-core.js +1553 -0
- zerochat_runtime/assets/js/api.js +844 -0
- zerochat_runtime/assets/js/app.js +2558 -0
- zerochat_runtime/assets/js/attachments.js +220 -0
- zerochat_runtime/assets/js/charts.js +338 -0
- zerochat_runtime/assets/js/chat-engine.js +566 -0
- zerochat_runtime/assets/js/config-store.js +207 -0
- zerochat_runtime/assets/js/context-manager.js +584 -0
- zerochat_runtime/assets/js/conversation-service.js +484 -0
- zerochat_runtime/assets/js/cookies.js +688 -0
- zerochat_runtime/assets/js/data-reset-service.js +136 -0
- zerochat_runtime/assets/js/debug.js +508 -0
- zerochat_runtime/assets/js/defaults.js +16 -0
- zerochat_runtime/assets/js/export.js +179 -0
- zerochat_runtime/assets/js/file-parser.js +2088 -0
- zerochat_runtime/assets/js/generation-controller.js +417 -0
- zerochat_runtime/assets/js/i18n.js +1483 -0
- zerochat_runtime/assets/js/icons.js +156 -0
- zerochat_runtime/assets/js/ingestionEngine.js +436 -0
- zerochat_runtime/assets/js/markdown.js +588 -0
- zerochat_runtime/assets/js/mcp.js +1271 -0
- zerochat_runtime/assets/js/message-turns.js +62 -0
- zerochat_runtime/assets/js/profile-backup.js +118 -0
- zerochat_runtime/assets/js/profile-export-bundle.js +481 -0
- zerochat_runtime/assets/js/profile-repository.js +213 -0
- zerochat_runtime/assets/js/providers-webllm.js +486 -0
- zerochat_runtime/assets/js/providers.js +1560 -0
- zerochat_runtime/assets/js/rag-index.js +295 -0
- zerochat_runtime/assets/js/rag-service.js +530 -0
- zerochat_runtime/assets/js/rag-ui.js +997 -0
- zerochat_runtime/assets/js/ragStorage.js +676 -0
- zerochat_runtime/assets/js/sandbox.js +449 -0
- zerochat_runtime/assets/js/state.js +704 -0
- zerochat_runtime/assets/js/storage-db.js +144 -0
- zerochat_runtime/assets/js/tool-cards.js +330 -0
- zerochat_runtime/assets/js/tool-security.js +653 -0
- zerochat_runtime/assets/js/tools/README.md +50 -0
- zerochat_runtime/assets/js/tools/builtin/agent-checkpoint.tool.js +212 -0
- zerochat_runtime/assets/js/tools/builtin/download-pdf.tool.js +111 -0
- zerochat_runtime/assets/js/tools/builtin/execute-javascript.tool.js +219 -0
- zerochat_runtime/assets/js/tools/builtin/fetch-web-page.tool.js +112 -0
- zerochat_runtime/assets/js/tools/builtin/list-documents.tool.js +117 -0
- zerochat_runtime/assets/js/tools/builtin/read-knowledge-chunk.tool.js +129 -0
- zerochat_runtime/assets/js/tools/builtin/read-knowledge-image.tool.js +95 -0
- zerochat_runtime/assets/js/tools/builtin/render-chart.tool.js +90 -0
- zerochat_runtime/assets/js/tools/builtin/search-knowledge-base.tool.js +124 -0
- zerochat_runtime/assets/js/tools/builtin/search-web.tool.js +125 -0
- zerochat_runtime/assets/js/tools/tool-manifest.js +53 -0
- zerochat_runtime/assets/js/tools/tool-runtime.js +77 -0
- zerochat_runtime/assets/js/ui-composer.js +319 -0
- zerochat_runtime/assets/js/ui-conversation.js +655 -0
- zerochat_runtime/assets/js/ui-dialogs.js +132 -0
- zerochat_runtime/assets/js/ui-generation-status.js +119 -0
- zerochat_runtime/assets/js/ui-inspector.js +804 -0
- zerochat_runtime/assets/js/ui-mcp.js +604 -0
- zerochat_runtime/assets/js/ui-profiles.js +824 -0
- zerochat_runtime/assets/js/ui-reasoning.js +146 -0
- zerochat_runtime/assets/js/ui-settings.js +825 -0
- zerochat_runtime/assets/js/ui-shell.js +215 -0
- zerochat_runtime/assets/js/ui-sidebar.js +429 -0
- zerochat_runtime/assets/js/ui-telemetry.js +404 -0
- zerochat_runtime/assets/js/ui-transfer.js +262 -0
- zerochat_runtime/assets/js/utils.js +216 -0
- zerochat_runtime/assets/js/vendor/orama.browser.js +11 -0
- zerochat_runtime/assets/js/web-browser.js +569 -0
- zerochat_runtime/assets/js/web-search.js +519 -0
- zerochat_runtime/assets/manifest.webmanifest +20 -0
- zerochat_runtime/assets/services/dummy_mcp/dummy_mcp_server.py +47 -0
- zerochat_runtime/assets/services/dummy_mcp/service.json +22 -0
- zerochat_runtime/assets/services/lsp/installer.json +9 -0
- zerochat_runtime/assets/services/lsp/service.json +22 -0
- zerochat_runtime/assets/services/memory/installer.json +9 -0
- zerochat_runtime/assets/services/memory/service.json +24 -0
- zerochat_runtime/assets/services/playwright/installer.json +10 -0
- zerochat_runtime/assets/services/playwright/service.json +39 -0
- zerochat_runtime/assets/sw.js +164 -0
- 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
|
+
});
|