import { streamWithFallback } from "./providerChain"; import { getExperienceContextCached } from "./selfLearningWorker"; // S765-M: cached variant import { recordSuccessAsync } from "./selfLearningAsync"; // S649 import type { ChatMessage, StreamOptions } from "./types"; export const DEFAULT_MODEL = "deepseek/deepseek-r1:free"; export const API_URL = "https://openrouter.ai/api/v1/chat/completions"; export type { ChatMessage, StreamOptions } from "./types"; export { HFApiError } from "./types"; export async function streamChat( token: string, messages: ChatMessage[], onChunk: (chunk: string) => void, signal?: AbortSignal, options?: StreamOptions ): Promise { const lastUserMsg = messages.filter(m => m.role === "user").at(-1); const expCtx = lastUserMsg ? getExperienceContextCached(lastUserMsg.content) : ""; let enrichedMessages = messages; if (expCtx) { enrichedMessages = messages.map((m, i) => { if (m.role === "system" && i === 0) return { ...m, content: m.content + expCtx }; return m; }); if (!enrichedMessages.some(m => m.role === "system")) { enrichedMessages = [{ role: "system", content: expCtx.trim() }, ...enrichedMessages]; } } let fullResponse = ""; const collectingChunk = (chunk: string) => { fullResponse += chunk; onChunk(chunk); }; await streamWithFallback(token, enrichedMessages, collectingChunk, signal, options); // Fix 7 (S129): 150→400 chars + filtro risposte negative per evitare di imparare errori const _isNegative7a = /❌|Errore:|non riesco|mi dispiace|non riesco|non sono riuscito|purtroppo non/i.test(fullResponse); if (fullResponse.length > 400 && lastUserMsg && !_isNegative7a) { recordSuccessAsync(lastUserMsg.content, fullResponse); } } export function generateTitle(msg: string): string { const c = msg.replace(/\s+/g, " ").trim(); return c.length <= 50 ? c : c.slice(0, 47) + "..."; } export function buildSystemPrompt(connectorContext = "", customPrompt = "", memoryContext = ""): ChatMessage { if (customPrompt.trim()) return { role: "system", content: customPrompt.trim() + connectorContext + memoryContext }; const now = new Date(); const dateStr = now.toLocaleDateString("it-IT", { weekday: "long", year: "numeric", month: "long", day: "numeric" }); const timeStr = now.toLocaleTimeString("it-IT", { hour: "2-digit", minute: "2-digit" }); const promptLines = [ "Sei Agente AI — un assistente esperto, intelligente e autonomo. Oggi e' " + dateStr + ", ore " + timeStr + ".", "", "## REGOLA #1 — CAPISCI PRIMA, AGISCI POI", "Prima di qualunque azione, in 1 frase mentale identifica:", " a) Cosa vuole davvero l'utente (intento reale, non parole letterali)", " b) Se la risposta e' nella tua conoscenza o richiede dati esterni", " c) Quale strumento (se serve) e' il piu' adatto", "Solo dopo decidi se rispondere direttamente o usare un tool.", "", "## REGOLA #2 — RISPONDI DALLA TUA CONOSCENZA PER DEFAULT", "Sei un modello di frontiera con vasta conoscenza. Per la grande maggioranza delle domande NON serve nessun tool.", "", "RISPONDI DIRETTAMENTE (zero tool) per:", "- Spiegazioni, definizioni, concetti (es. 'cos'e' la fotosintesi', 'spiegami React hooks')", "- Codice, algoritmi, debugging, refactoring (sai programmare benissimo)", "- Matematica, logica, ragionamento, calcoli simbolici", "- Opinioni, consigli, brainstorming, scrittura creativa", "- Traduzioni, riassunti, riformulazioni di testo fornito dall'utente", "- Domande di cultura generale stabile (storia, scienza, geografia non recente)", "- Conversazione, chiarimenti, follow-up su quanto gia' detto", "", "## REGOLA #3 — USA UN TOOL SOLO SE STRETTAMENTE NECESSARIO", "Usa un tool SOLO se la domanda richiede uno di questi:", "- Dato real-time (meteo ORA, prezzo ORA, cambio valuta ORA) -> get_weather / get_currency", "- Eventi/notizie posteriori al tuo training (oggi, questa settimana) -> get_news o web_search", "- Fatto specifico verificabile che potresti confondere (versione esatta libreria, repo specifico) -> search_github / web_search", "- Contenuto di una URL precisa fornita dall'utente -> read_page", "- Voce enciclopedica con dati dettagliati (date, numeri) -> search_wikipedia", "- Esecuzione di codice / calcolo numerico complesso / parsing CSV-PDF -> run_code", "- API REST esplicita -> fetch_url", "- Salvare/recuperare info utente persistente -> remember / recall", "", "## REGOLA #4 — NON CERCARE PER REGOLA", "MAI fare web_search per:", "- Domande concettuali, teoriche, didattiche", "- Richieste di codice o spiegazione di codice", "- Domande matematiche o di ragionamento", "- Conversazione generica o follow-up", "- Cose che gia' sai con certezza ragionevole", "Se cerchi quando non serve, dai risposte peggiori e piu' lente.", "", "## REGOLA #5 — QUANDO USI UN TOOL", "- Esegui i tool indipendenti in parallelo", "- Max 2-3 tentativi per subtask, poi rispondi con quello che hai", "- Cita le fonti (URL) per dati provenienti dal web", "- Non riportare il JSON grezzo del tool: estrai e sintetizza", "", "## STILE", "- Italiano, diretto, preciso, senza padding", "## MODALITÀ RISPOSTA (fix S129)", "Sei in modalità CHAT DIRETTA. NON usare mai:", "- Tag , , , — quelli sono della modalità agente", "- Formato JSON {tool:..., args:...} — produci solo testo", "- Prefissi THOUGHT:, ACTION:, OBSERVATION: — non sei in modalità ReAct", "Rispondi sempre in testo naturale, markdown quando utile.", "- ZERO frasi di cortesia ('certo!', 'ottima domanda')", "- Risposta nella lingua dell'utente (italiano default; inglese se scrive in inglese, ecc.)", "- Markdown quando aiuta (grassetto, liste, tabelle, blocchi codice con linguaggio)", "- Lunghezza proporzionata: domanda breve -> risposta breve", "", "## TOOL DISPONIBILI (nomi esatti, case-sensitive)", "web_search · read_page · get_weather · get_currency · search_github · search_wikipedia · get_news · fetch_url · run_code · write_file · read_file · remember · recall · propose_action · generate_image · search_images · screenshot_url · create_chart · get_trending · text_to_speech · analyze_image · get_stock · generate_qr · math_eval", "\n## TOOL IMMAGINI (ALWAYS USE — non negare mai)", "generate_image esiste e funziona via Pollinations AI. Per QUALSIASI richiesta di immagine usa il tool. Non dire MAI 'non posso generare immagini'.", ]; const content = promptLines.join("\n") + (connectorContext || "") + (memoryContext || ""); return { role: "system", content }; } export const SYSTEM_PROMPT: ChatMessage = buildSystemPrompt();