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,519 @@
1
+ /**
2
+ * Módulo de Búsqueda Web Desacoplado (ChatWebSearch) para ZeroChat.
3
+ * - Arquitectura basada en SearchProvider con registro extensible (DuckDuckGo, SearXNG, etc.).
4
+ * - Motor de búsqueda resiliente multitrayecto con fallback automático.
5
+ * - Formateo estandarizado de resultados en Markdown optimizado para LLMs.
6
+ * - Compatible con entornos file://, http:// y Node.js.
7
+ */
8
+
9
+ (function (root, factory) {
10
+ if (typeof exports === 'object' && typeof module !== 'undefined') {
11
+ module.exports = factory();
12
+ } else {
13
+ root.ChatWebSearch = factory();
14
+ }
15
+ })(typeof self !== 'undefined' ? self : this, function () {
16
+ 'use strict';
17
+
18
+ const DEFAULT_TIMEOUT_MS = 10000;
19
+
20
+ function getUtils() {
21
+ if (typeof window !== 'undefined' && window.ChatUtils) return window.ChatUtils;
22
+ if (typeof require !== 'undefined') {
23
+ try { return require('./utils.js'); } catch (e) {}
24
+ }
25
+ return null;
26
+ }
27
+
28
+ /**
29
+ * Realiza una petición fetch con timeout controlado mediante AbortController.
30
+ */
31
+ async function fetchWithTimeout(url, options = {}, timeoutMs = DEFAULT_TIMEOUT_MS) {
32
+ const Utils = getUtils();
33
+ if (Utils && typeof Utils.fetchWithTimeout === 'function') {
34
+ return Utils.fetchWithTimeout(url, options, timeoutMs);
35
+ }
36
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
37
+ const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
38
+
39
+ try {
40
+ return await fetch(url, {
41
+ ...options,
42
+ signal: controller ? controller.signal : undefined
43
+ });
44
+ } finally {
45
+ if (timer) clearTimeout(timer);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Desempaqueta URLs redirigidas de DuckDuckGo (parámetro uddg).
51
+ */
52
+ function unwrapDdgUrl(rawUrl) {
53
+ if (!rawUrl) return '';
54
+ const match = rawUrl.match(/[?&]uddg=([^&]+)/);
55
+ if (match) {
56
+ try {
57
+ return decodeURIComponent(match[1]);
58
+ } catch (e) {
59
+ return match[1];
60
+ }
61
+ }
62
+ if (rawUrl.startsWith('//')) return 'https:' + rawUrl;
63
+ return rawUrl;
64
+ }
65
+
66
+ /**
67
+ * Clase Base para proveedores de búsqueda web (SearchProvider).
68
+ */
69
+ class BaseSearchProvider {
70
+ constructor(options = {}) {
71
+ this.id = options.id || 'base';
72
+ this.label = options.label || 'Base Search Provider';
73
+ }
74
+
75
+ /**
76
+ * Realiza una búsqueda web y retorna una estructura estandarizada.
77
+ * @param {string} query - Consulta de búsqueda.
78
+ * @param {object} options - Opciones de búsqueda (lang, timeoutMs, etc.).
79
+ * @returns {Promise<{ success: boolean, query: string, count: number, results: Array, markdown: string, elapsedMs: number, error?: string }>}
80
+ */
81
+ async search(query, options = {}) {
82
+ throw new Error(`El método search() debe ser implementado en ${this.constructor.name}`);
83
+ }
84
+
85
+ /**
86
+ * Formatea los resultados en Markdown estructurado y compacto para el asistente.
87
+ */
88
+ formatMarkdown(query, results = [], lang = 'es') {
89
+ const isEn = (lang === 'en');
90
+ const cleanQuery = (query || '').trim();
91
+
92
+ if (!results || results.length === 0) {
93
+ return isEn
94
+ ? `[${this.label} search for "${cleanQuery}"]: No direct results found. Try searching with different terms or use \`fetch_web_page\` directly with a specific URL.`
95
+ : `[Búsqueda ${this.label} para "${cleanQuery}"]: No se encontraron resultados directos. Puedes intentar buscar con otros términos o usar directamente la herramienta \`fetch_web_page\` con una URL concreta.`;
96
+ }
97
+
98
+ let md = isEn
99
+ ? `### 🔍 ${this.label} search results for: "${cleanQuery}" (${results.length} results found):\n\n`
100
+ : `### 🔍 Resultados de búsqueda ${this.label} para: "${cleanQuery}" (${results.length} resultados encontrados):\n\n`;
101
+
102
+ results.forEach((item, idx) => {
103
+ md += `${idx + 1}. **[${item.title}](${item.url})**\n`;
104
+ md += ` - *${isEn ? 'Source' : 'Fuente'}:* \`${item.source || this.label}\`\n`;
105
+ if (item.snippet) {
106
+ md += ` - *${isEn ? 'Snippet' : 'Resumen'}:* ${item.snippet}\n`;
107
+ }
108
+ md += ` - *${isEn ? 'Link' : 'Enlace'}:* ${item.url}\n\n`;
109
+ });
110
+
111
+ return md;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Proveedor de búsqueda DuckDuckGo con arquitectura multitrayecto y fallbacks.
117
+ */
118
+ class DuckDuckGoSearchProvider extends BaseSearchProvider {
119
+ constructor(options = {}) {
120
+ super({
121
+ id: 'duckduckgo',
122
+ label: 'DuckDuckGo',
123
+ ...options
124
+ });
125
+ }
126
+
127
+ /**
128
+ * Parsea resultados de DuckDuckGo desde Markdown de Reader Gateway.
129
+ */
130
+ parseMarkdownResults(md) {
131
+ const results = [];
132
+ const seenUrls = new Set();
133
+ const sections = (md || '').split(/\n(?=##\s*\[)/);
134
+
135
+ for (const sec of sections) {
136
+ const titleMatch = sec.match(/^##\s*\[([^\]]+)\]\(([^)]+)\)/);
137
+ if (!titleMatch) continue;
138
+ const rawTitle = titleMatch[1].trim();
139
+ const rawUrl = titleMatch[2].trim();
140
+ const realUrl = unwrapDdgUrl(rawUrl);
141
+
142
+ if (!realUrl || seenUrls.has(realUrl.toLowerCase()) || realUrl.includes('duckduckgo.com/html') || realUrl.includes('duckduckgo.com/?q=')) {
143
+ continue;
144
+ }
145
+ seenUrls.add(realUrl.toLowerCase());
146
+
147
+ const linkMatches = [...sec.matchAll(/\[([^\]]{25,})\]\([^)]+\)/g)];
148
+ let snippet = '';
149
+ if (linkMatches.length > 0) {
150
+ snippet = linkMatches[linkMatches.length - 1][1].replace(/\*\*/g, '').trim();
151
+ } else {
152
+ const lines = sec.split('\n').filter(l => !l.startsWith('##') && !l.startsWith('[!') && l.trim().length > 20);
153
+ if (lines.length > 0) {
154
+ snippet = lines.join(' ').replace(/\[([^\]]+)\]\([^)]+\)/g, '$1').replace(/\*\*/g, '').trim();
155
+ }
156
+ }
157
+
158
+ results.push({
159
+ title: rawTitle,
160
+ url: realUrl,
161
+ snippet: snippet,
162
+ source: 'DuckDuckGo Web'
163
+ });
164
+ }
165
+ return results;
166
+ }
167
+
168
+ /**
169
+ * Parsea resultados de DuckDuckGo desde HTML crudo.
170
+ */
171
+ parseHtmlResults(html) {
172
+ const results = [];
173
+ const seenUrls = new Set();
174
+ const blockRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?(?:<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>|<div[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/div>|$)/g;
175
+
176
+ let match;
177
+ while ((match = blockRegex.exec(html || '')) !== null) {
178
+ const rawUrl = match[1].trim();
179
+ const rawTitle = match[2].replace(/<[^>]*>/g, '').trim();
180
+ const snippet = (match[3] || match[4] || '').replace(/<[^>]*>/g, '').trim();
181
+ const realUrl = unwrapDdgUrl(rawUrl);
182
+
183
+ if (realUrl && !seenUrls.has(realUrl.toLowerCase()) && !realUrl.includes('duckduckgo.com/html')) {
184
+ seenUrls.add(realUrl.toLowerCase());
185
+ results.push({
186
+ title: rawTitle,
187
+ url: realUrl,
188
+ snippet: snippet,
189
+ source: 'DuckDuckGo Web'
190
+ });
191
+ }
192
+ }
193
+ return results;
194
+ }
195
+
196
+ /**
197
+ * Consulta DuckDuckGo Instant Answer API.
198
+ */
199
+ async queryInstantAnswerApi(query, timeoutMs = 4000) {
200
+ try {
201
+ const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&pretty=1`;
202
+ const res = await fetchWithTimeout(url, { headers: { 'Accept': 'application/json' } }, timeoutMs);
203
+ if (!res.ok) return [];
204
+ const data = await res.json();
205
+ const results = [];
206
+
207
+ if (data.Answer) {
208
+ results.push({
209
+ title: 'Respuesta Directa',
210
+ snippet: data.Answer,
211
+ url: data.AbstractURL || `https://duckduckgo.com/?q=${encodeURIComponent(query)}`,
212
+ source: 'DuckDuckGo Instant Answer'
213
+ });
214
+ }
215
+ if (data.AbstractText && data.AbstractURL) {
216
+ results.push({
217
+ title: data.Heading || query,
218
+ snippet: data.AbstractText,
219
+ url: data.AbstractURL,
220
+ source: data.AbstractSource ? `DuckDuckGo (${data.AbstractSource})` : 'DuckDuckGo Abstract'
221
+ });
222
+ }
223
+ if (data.Results && Array.isArray(data.Results)) {
224
+ data.Results.forEach(r => {
225
+ if (r.FirstURL && r.Text) {
226
+ results.push({
227
+ title: r.Text.split(' - ')[0] || r.Text,
228
+ snippet: r.Text,
229
+ url: r.FirstURL,
230
+ source: 'DuckDuckGo'
231
+ });
232
+ }
233
+ });
234
+ }
235
+ return results;
236
+ } catch (e) {
237
+ return [];
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Fallback de búsqueda en Wikipedia API.
243
+ */
244
+ async queryWikipediaApi(query, timeoutMs = 4000) {
245
+ try {
246
+ const wikiUrl = `https://es.wikipedia.org/w/api.php?action=opensearch&search=${encodeURIComponent(query)}&limit=5&namespace=0&format=json&origin=*`;
247
+ const res = await fetchWithTimeout(wikiUrl, {}, timeoutMs);
248
+ if (res.ok) {
249
+ const data = await res.json();
250
+ if (data && Array.isArray(data) && data[1] && data[1].length > 0) {
251
+ const wikiResults = [];
252
+ for (let i = 0; i < data[1].length; i++) {
253
+ wikiResults.push({
254
+ title: data[1][i],
255
+ snippet: data[2] ? data[2][i] || '' : '',
256
+ url: data[3] ? data[3][i] : '',
257
+ source: 'Wikipedia'
258
+ });
259
+ }
260
+ return wikiResults;
261
+ }
262
+ }
263
+ } catch (e) {}
264
+ return [];
265
+ }
266
+
267
+ async search(query, options = {}) {
268
+ const cleanQuery = (query || '').trim();
269
+ const lang = options.lang || 'es';
270
+ const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
271
+
272
+ if (!cleanQuery) {
273
+ return {
274
+ success: false,
275
+ query: '',
276
+ count: 0,
277
+ results: [],
278
+ markdown: 'No se especificó ninguna consulta de búsqueda.',
279
+ elapsedMs: 0,
280
+ error: 'Consulta vacía'
281
+ };
282
+ }
283
+
284
+ const startTime = typeof performance !== 'undefined' ? performance.now() : Date.now();
285
+ let rawResults = [];
286
+
287
+ // 1. Vía Universal Reader Gateway (con bypass CORS transparente para resultados web completos)
288
+ try {
289
+ const targetUrl = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(cleanQuery)}`;
290
+ const readerUrl = `https://r.jina.ai/${targetUrl}`;
291
+ const res = await fetchWithTimeout(readerUrl, { headers: { 'Accept': 'text/plain' } }, Math.min(timeoutMs, 7000));
292
+ if (res.ok) {
293
+ const md = await res.text();
294
+ const parsed = this.parseMarkdownResults(md);
295
+ if (parsed.length > 0) {
296
+ rawResults = parsed;
297
+ }
298
+ }
299
+ } catch (e) {
300
+ console.warn('Reader Gateway no disponible para búsqueda DuckDuckGo, intentando vía directa:', e.message);
301
+ }
302
+
303
+ // 2. Vía Directa a DuckDuckGo HTML
304
+ if (rawResults.length === 0) {
305
+ try {
306
+ const targetUrl = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(cleanQuery)}`;
307
+ const res = await fetchWithTimeout(targetUrl, {
308
+ headers: {
309
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
310
+ }
311
+ }, Math.min(timeoutMs, 5000));
312
+ if (res.ok) {
313
+ const html = await res.text();
314
+ const parsed = this.parseHtmlResults(html);
315
+ if (parsed.length > 0) {
316
+ rawResults = parsed;
317
+ }
318
+ }
319
+ } catch (e) {
320
+ console.warn('DuckDuckGo HTML directo no disponible:', e.message);
321
+ }
322
+ }
323
+
324
+ // 3. Vía DuckDuckGo Instant Answer API
325
+ if (rawResults.length === 0) {
326
+ const instantResults = await this.queryInstantAnswerApi(cleanQuery, Math.min(timeoutMs, 4000));
327
+ if (instantResults.length > 0) {
328
+ rawResults = instantResults;
329
+ }
330
+ }
331
+
332
+ // 4. Vía Wikipedia Search API
333
+ if (rawResults.length === 0) {
334
+ const wikiResults = await this.queryWikipediaApi(cleanQuery, Math.min(timeoutMs, 4000));
335
+ if (wikiResults.length > 0) {
336
+ rawResults = wikiResults;
337
+ }
338
+ }
339
+
340
+ const endTime = typeof performance !== 'undefined' ? performance.now() : Date.now();
341
+ const elapsed = parseFloat((endTime - startTime).toFixed(2));
342
+ const mdOutput = this.formatMarkdown(cleanQuery, rawResults, lang);
343
+
344
+ return {
345
+ success: rawResults.length > 0,
346
+ query: cleanQuery,
347
+ count: rawResults.length,
348
+ results: rawResults,
349
+ markdown: mdOutput,
350
+ elapsedMs: elapsed
351
+ };
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Proveedor de búsqueda compatible con instancias SearXNG.
357
+ */
358
+ class SearXNGSearchProvider extends BaseSearchProvider {
359
+ constructor(options = {}) {
360
+ super({
361
+ id: 'searxng',
362
+ label: 'SearXNG',
363
+ ...options
364
+ });
365
+ this.baseUrl = (options.baseUrl || 'https://searx.be').trim().replace(/\/+$/, '');
366
+ }
367
+
368
+ /**
369
+ * Parsea los resultados del formato JSON estándar de SearXNG.
370
+ */
371
+ parseJsonResponse(data) {
372
+ if (!data || !Array.isArray(data.results)) return [];
373
+ return data.results.slice(0, 10).map(r => ({
374
+ title: r.title || 'Sin título',
375
+ url: r.url || '',
376
+ snippet: r.content || r.snippet || '',
377
+ source: `SearXNG (${r.engine || 'web'})`
378
+ })).filter(r => !!r.url);
379
+ }
380
+
381
+ async search(query, options = {}) {
382
+ const cleanQuery = (query || '').trim();
383
+ const lang = options.lang || 'es';
384
+ const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
385
+
386
+ if (!cleanQuery) {
387
+ return {
388
+ success: false,
389
+ query: '',
390
+ count: 0,
391
+ results: [],
392
+ markdown: 'No se especificó ninguna consulta de búsqueda.',
393
+ elapsedMs: 0,
394
+ error: 'Consulta vacía'
395
+ };
396
+ }
397
+
398
+ const startTime = typeof performance !== 'undefined' ? performance.now() : Date.now();
399
+ let rawResults = [];
400
+
401
+ try {
402
+ const url = `${this.baseUrl}/search?q=${encodeURIComponent(cleanQuery)}&format=json&language=${lang === 'en' ? 'en' : 'es'}`;
403
+ const res = await fetchWithTimeout(url, { headers: { 'Accept': 'application/json' } }, timeoutMs);
404
+ if (res.ok) {
405
+ const data = await res.json();
406
+ rawResults = this.parseJsonResponse(data);
407
+ }
408
+ } catch (err) {
409
+ console.warn(`Error consultando SearXNG (${this.baseUrl}):`, err.message);
410
+ }
411
+
412
+ const endTime = typeof performance !== 'undefined' ? performance.now() : Date.now();
413
+ const elapsed = parseFloat((endTime - startTime).toFixed(2));
414
+ const mdOutput = this.formatMarkdown(cleanQuery, rawResults, lang);
415
+
416
+ return {
417
+ success: rawResults.length > 0,
418
+ query: cleanQuery,
419
+ count: rawResults.length,
420
+ results: rawResults,
421
+ markdown: mdOutput,
422
+ elapsedMs: elapsed
423
+ };
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Registro central de proveedores de búsqueda (SearchProviderRegistry).
429
+ */
430
+ class SearchProviderRegistry {
431
+ constructor() {
432
+ this.providers = new Map();
433
+ this.defaultProviderId = 'duckduckgo';
434
+
435
+ // Registro de proveedores iniciales
436
+ this.register(new DuckDuckGoSearchProvider());
437
+ this.register(new SearXNGSearchProvider());
438
+ }
439
+
440
+ /**
441
+ * Registra un proveedor de búsqueda.
442
+ */
443
+ register(provider) {
444
+ if (provider && provider.id) {
445
+ this.providers.set(provider.id.toLowerCase(), provider);
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Obtiene un proveedor por su identificador.
451
+ */
452
+ get(id) {
453
+ if (!id) return this.providers.get(this.defaultProviderId);
454
+ return this.providers.get(String(id).toLowerCase()) || this.providers.get(this.defaultProviderId);
455
+ }
456
+
457
+ /**
458
+ * Establece el proveedor predeterminado.
459
+ */
460
+ setDefault(id) {
461
+ if (id && this.providers.has(String(id).toLowerCase())) {
462
+ this.defaultProviderId = String(id).toLowerCase();
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Ejecuta una búsqueda utilizando el proveedor indicado o el por defecto.
468
+ */
469
+ async search(query, options = {}) {
470
+ const providerId = options.providerId || options.provider;
471
+ const provider = this.get(providerId);
472
+ return provider.search(query, options);
473
+ }
474
+ }
475
+
476
+ /**
477
+ * Módulo de Búsqueda Web Desacoplado (ChatWebSearch) para ZeroChat.
478
+ */
479
+ /* ... */
480
+ /**
481
+ * Función de búsqueda web compatible con la interfaz histórica de ZeroChat.
482
+ */
483
+ async function search(query, lang = 'es', options = {}) {
484
+ return registry.search(query, { lang, ...options });
485
+ }
486
+
487
+ const registry = new SearchProviderRegistry();
488
+
489
+ /**
490
+ * Definición estándar de herramienta (Tool/Function Calling) para OpenAI y LLMs compatibles.
491
+ */
492
+ const SEARCH_TOOL_DEFINITION = {
493
+ type: 'function',
494
+ function: {
495
+ name: 'search_web',
496
+ description: 'Busca en internet en tiempo real información actualizada, noticias, artículos y enlaces web utilizando DuckDuckGo.',
497
+ parameters: {
498
+ type: 'object',
499
+ properties: {
500
+ query: {
501
+ type: 'string',
502
+ description: 'Términos o consulta de búsqueda en DuckDuckGo (ej: "INE poblacion Ceuta padron", "DeepSeek R1").'
503
+ }
504
+ },
505
+ required: ['query']
506
+ }
507
+ }
508
+ };
509
+
510
+ return {
511
+ search,
512
+ SEARCH_TOOL_DEFINITION,
513
+ BaseSearchProvider,
514
+ DuckDuckGoSearchProvider,
515
+ SearXNGSearchProvider,
516
+ SearchProviderRegistry,
517
+ registry
518
+ };
519
+ });
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "ZeroChat",
3
+ "short_name": "ZeroChat",
4
+ "description": "Cliente Web Universal de Chat, Agente IA Autónomo y RAG Local",
5
+ "start_url": "./zerochat.html",
6
+ "scope": "./",
7
+ "display": "standalone",
8
+ "orientation": "any",
9
+ "background_color": "#0d1117",
10
+ "theme_color": "#0d1117",
11
+ "icons": [
12
+ {
13
+ "src": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%233b82f6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='4 17 10 11 4 5'%3E%3C/polyline%3E%3Cline x1='12' y1='19' x2='20' y2='19'%3E%3C/line%3E%3C/svg%3E",
14
+ "sizes": "192x192 512x512",
15
+ "type": "image/svg+xml",
16
+ "purpose": "any"
17
+ }
18
+ ]
19
+ }
20
+
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import sys
4
+
5
+ def reply(req_id, result=None, error=None):
6
+ resp = {"jsonrpc": "2.0", "id": req_id}
7
+ if error:
8
+ resp["error"] = error
9
+ else:
10
+ resp["result"] = result
11
+ sys.stdout.write(json.dumps(resp) + "\n")
12
+ sys.stdout.flush()
13
+
14
+ for raw in sys.stdin:
15
+ try:
16
+ req = json.loads(raw)
17
+ except Exception:
18
+ continue
19
+ req_id = req.get("id")
20
+ method = req.get("method")
21
+ params = req.get("params", {})
22
+ if method == "initialize":
23
+ reply(req_id, {
24
+ "protocolVersion": "2024-11-05",
25
+ "serverInfo": {"name": "ZeroChat Dummy MCP", "version": "1.0.0"},
26
+ "capabilities": {"tools": {}}
27
+ })
28
+ elif method == "tools/list":
29
+ reply(req_id, {"tools": [{
30
+ "name": "echo",
31
+ "description": "Echo back a message for testing.",
32
+ "inputSchema": {
33
+ "type": "object",
34
+ "properties": {"message": {"type": "string", "description": "Message to echo."}},
35
+ "required": ["message"]
36
+ }
37
+ }]})
38
+ elif method == "tools/call":
39
+ if params.get("name") != "echo":
40
+ reply(req_id, error={"code": -32601, "message": "Tool not found"})
41
+ continue
42
+ msg = params.get("arguments", {}).get("message", "")
43
+ reply(req_id, {
44
+ "content": [{"type": "text", "text": f"echo: {msg}"}],
45
+ "isError": False
46
+ })
47
+
@@ -0,0 +1,22 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "dummy_mcp",
4
+ "displayName": {
5
+ "es": "MCP de prueba",
6
+ "en": "Test MCP"
7
+ },
8
+ "description": {
9
+ "es": "Servicio MCP mínimo para comprobar la infraestructura externa.",
10
+ "en": "Minimal MCP service for verifying the external infrastructure."
11
+ },
12
+ "enabledByDefault": false,
13
+ "transport": "stdio",
14
+ "launch": {
15
+ "executable": "${pythonExecutable}",
16
+ "args": ["${serviceDir}/dummy_mcp_server.py"],
17
+ "cwd": "${serviceDir}",
18
+ "env": {},
19
+ "handshakeTimeoutSeconds": 10
20
+ }
21
+ }
22
+
@@ -0,0 +1,9 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "type": "npm",
4
+ "product": {
5
+ "package": "@axivo/mcp-lsp",
6
+ "version": "1.0.5"
7
+ }
8
+ }
9
+
@@ -0,0 +1,22 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "lsp",
4
+ "displayName": {
5
+ "es": "LSP y Navegación de Código",
6
+ "en": "LSP & Code Intelligence"
7
+ },
8
+ "description": {
9
+ "es": "Servidor de protocolos de lenguaje (LSP): salto a definiciones, búsqueda de símbolos, referencias e inspección de tipos sin sobrecargar el contexto.",
10
+ "en": "Language Server Protocol (LSP) server: jump to definitions, symbol search, references, and type inspection without context overload."
11
+ },
12
+ "enabledByDefault": false,
13
+ "transport": "stdio",
14
+ "launch": {
15
+ "executable": "${nodeExecutable}",
16
+ "args": ["${serviceDir}/node_modules/@axivo/mcp-lsp/dist/index.js"],
17
+ "cwd": "${serviceDir}",
18
+ "env": {},
19
+ "handshakeTimeoutSeconds": 30
20
+ }
21
+ }
22
+
@@ -0,0 +1,9 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "type": "npm",
4
+ "product": {
5
+ "package": "@modelcontextprotocol/server-memory",
6
+ "version": "2026.8.31"
7
+ }
8
+ }
9
+
@@ -0,0 +1,24 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "memory",
4
+ "displayName": {
5
+ "es": "Memoria y Grafos (Knowledge Graph)",
6
+ "en": "Memory & Knowledge Graph"
7
+ },
8
+ "description": {
9
+ "es": "Almacenamiento persistente de entidades, preferencias y contexto histórico estructurado en un grafo de conocimiento.",
10
+ "en": "Persistent storage of entities, preferences, and historical context structured as a knowledge graph."
11
+ },
12
+ "enabledByDefault": false,
13
+ "transport": "stdio",
14
+ "launch": {
15
+ "executable": "${nodeExecutable}",
16
+ "args": ["${serviceDir}/node_modules/@modelcontextprotocol/server-memory/dist/index.js"],
17
+ "cwd": "${serviceDir}",
18
+ "env": {
19
+ "MEMORY_FILE_PATH": "${serviceDir}/memory.jsonl"
20
+ },
21
+ "handshakeTimeoutSeconds": 30
22
+ }
23
+ }
24
+
@@ -0,0 +1,10 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "type": "npm",
4
+ "product": {
5
+ "package": "@playwright/mcp",
6
+ "version": "0.0.81",
7
+ "browser": "chromium"
8
+ }
9
+ }
10
+