// ═══════════════════════════════════════════════════════════ // ETXEAPALA AGENTS — punto de entrada + utilidades // ═══════════════════════════════════════════════════════════ // Todos los agentes se comunican con la API de Claude a través // de /ai. Por defecto usan Haiku (más barato y rápido). // Sonnet solo se usa cuando el supervisor detecta baja confianza // o cuando se reintentan campos concretos con precisión alta. // ═══════════════════════════════════════════════════════════ const MODEL_FAST = 'claude-haiku-4-5'; // extracción y clasificación const MODEL_SMART = 'claude-sonnet-4-20250514'; // reintento y casos complejos // Llamada base al endpoint de IA async function etxeaAI(messages, opts = {}) { const { maxTokens = 800, model = MODEL_FAST } = opts; try { const resp = await fetch('/ai', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model, max_tokens: maxTokens, messages }) }); const data = await resp.json(); const texto = data.content?.map(c => c.text || '').join('').trim(); return { texto, raw: data }; } catch (err) { return { error: err.message }; } } // Parseo seguro de JSON function etxeaParseJSON(texto) { if (!texto) return null; try { return JSON.parse(texto.replace(/```json|```/g, '').trim()); } catch (e) { return null; } } // ─── CACHE DE ARCHIVOS ────────────────────────────────────── // Evita convertir el mismo archivo a base64 varias veces const _fileCache = new WeakMap(); async function fileToCachedBase64(file) { if (_fileCache.has(file)) return _fileCache.get(file); const base64 = await fileToBase64(file); _fileCache.set(file, base64); return base64; } // Construye el content item correcto según tipo async function buildFileContent(file) { const base64 = await fileToCachedBase64(file); if (file.type === 'application/pdf') { return { type: 'document', source: { type: 'base64', media_type: 'application/pdf', data: base64 } }; } return { type: 'image', source: { type: 'base64', media_type: file.type, data: base64 } }; }