Spaces:
Sleeping
Sleeping
File size: 9,510 Bytes
95eb75a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | /**
* errorNarrator.ts — S768+S769: traduzione errori tecnici → narrativa italiana user-friendly
*
* Centralizza TUTTA la narrativa utente che non riguarda i tool running state
* (i tool running state sono in toolStatusLabels.ts).
*
* Architettura:
* toolStatusLabels.ts → step running state label (narrativeLabel)
* errorNarrator.ts → tutto il resto:
* narrateApiError() errori HTTP provider
* narrateProviderFallback() fallback chain attivata
* narrateProviderSelected() provider selezionato post-fallback ← S769
* narrateLoopPhase() fasi del loop (elaborating/direct/…) ← S769
*
* Pattern: module-level cb registrato da agentLoop.ts via setNarratorStatusCb().
* Nessuna dipendenza React / store. Zero breaking changes.
*
* @module errorNarrator
*/
// ─── Module-level narration callback ─────────────────────────────────────────
let _narratorCb: ((msg: string) => void) | null = null;
/**
* Registra il callback onStatus del loop corrente.
* Chiamare subito dopo setPyodideStatus(onStatus) in agentLoop.ts.
*/
export function setNarratorStatusCb(cb: ((msg: string) => void) | null): void {
_narratorCb = cb;
}
/** Emette sul callback registrato O su quello esplicito (priorità a quello esplicito). */
function _emit(msg: string, explicitCb?: (msg: string) => void): void {
try { (explicitCb ?? _narratorCb)?.(msg); } catch { /* non-fatal */ }
}
// ─── Provider friendly names ─────────────────────────────────────────────────
const _PROVIDER_NAMES: Record<string, string> = {
groq: "Groq",
"groq-fast": "Groq Fast",
cerebras: "Cerebras",
sambanova: "SambaNova",
gemini: "Gemini",
deepseek: "DeepSeek",
openrouter: "OpenRouter",
hf: "HuggingFace",
"hf-sl": "HuggingFace Serverless",
openai: "OpenAI",
cf: "Cloudflare AI",
github: "GitHub Models", // S-LOOP9: aggiunto per narrateApiError
together: "Together AI",
or: "OpenRouter",
};
function _providerLabel(key: string): string {
const base = (key ?? "").split(":")[0];
return _PROVIDER_NAMES[base] ?? key;
}
// ─── 1. API Error → narrativa ─────────────────────────────────────────────────
/**
* Traduce un errore HTTP API in un messaggio italiano comprensibile per l'utente.
*
* @param status - Codice HTTP (401, 403, 429, 400, 422, 503, 500, …)
* @param errText - Body dell'errore (per rilevare quota/safety/model errors)
* @param provider - Chiave provider opzionale (es. "groq", "gemini")
* @param onStatus - Callback esplicito (opzionale — fallback al cb registrato)
*/
export function narrateApiError(
status: number,
errText: string,
provider?: string,
onStatus?: (msg: string) => void,
): void {
const cb = onStatus ?? _narratorCb;
if (!cb) return;
const provLabel = provider ? _providerLabel(provider) : "il provider AI";
const body = (errText ?? "").toLowerCase();
let msg: string;
if (status === 401 || status === 403) {
msg = `🔑 Le credenziali di ${provLabel} non sono valide — cambio provider, non si perde nulla`;
} else if (
status === 429 ||
body.includes("rate limit") ||
body.includes("quota") ||
body.includes("too many requests")
) {
msg = `⏳ ${provLabel} ha finito i turni disponibili — mi sposto su un altro provider`;
} else if (
body.includes("safety") ||
body.includes("content_filter") ||
body.includes("filtered") ||
body.includes("blocked by") ||
body.includes("policy violation")
) {
msg = `🛡️ Il filtro di sicurezza ha bloccato questa richiesta — la riformulo in modo diverso e riprovo`;
} else if (
body.includes("context_length") ||
body.includes("too long") ||
body.includes("max_tokens") ||
status === 413
) {
msg = `📏 La conversazione è diventata troppo lunga per ${provLabel} — la comprimo e riprovo subito`;
} else if (
body.includes("model_not_found") ||
body.includes("model not found") ||
body.includes("no such model") ||
status === 404
) {
msg = `🔄 Questo modello non è più disponibile su ${provLabel} — ne scelgo uno equivalente`;
} else if (
status === 503 ||
body.includes("overloaded") ||
body.includes("service unavailable") ||
body.includes("capacity")
) {
msg = `🔄 ${provLabel} è sotto pressione in questo momento — mi sposto su un'alternativa più libera`;
} else if (status === 400 || status === 422) {
msg = `⚠️ ${provLabel} non ha accettato la richiesta — la riformatto e riprovo con una configurazione diversa`;
} else if (status === 500) {
msg = `⚠️ ${provLabel} ha avuto un errore interno — cambio provider e ci riprovo`;
} else {
msg = `⚠️ ${provLabel} non risponde (errore ${status}) — passo a un provider alternativo`;
}
_emit(msg, onStatus);
}
// ─── 2. Provider fallback chain → narrativa ───────────────────────────────────
/**
* Emette un messaggio narrativo quando il routing smart passa al fallback chain.
* Chiamare nel blocco S196 di providerChain.ts (tutti i provider prioritari falliti).
*
* @param failedCount - Numero di provider prioritari falliti
* @param reasons - Array di motivi (da _loopReasons nel routing loop)
*/
export function narrateProviderFallback(
failedCount: number,
reasons: string[],
): void {
if (!_narratorCb) return;
const hasQuota = reasons.some(r => /quota|429|rate|limit/i.test(r));
const hasAuth = reasons.some(r => /auth|401|403|token/i.test(r));
const hasOverload = reasons.some(r => /overload|503|sovrac|capacity/i.test(r));
let msg: string;
if (hasAuth) {
msg = `🔑 Alcune chiavi API risultano non valide — provo i provider alternativi senza interrompere`;
} else if (hasQuota) {
msg = `⏳ I provider principali hanno esaurito i turni (${failedCount} su ${failedCount}) — attivo i canali di backup`;
} else if (hasOverload) {
msg = `🔄 I provider principali sono sotto carico — mi sposto sui modelli di riserva`;
} else {
msg = `🔄 ${failedCount} provider non hanno risposto — cerco il canale più veloce disponibile`;
}
_emit(msg);
}
// ─── 3. Provider selected after fallback → narrativa ──────────────────────────
/**
* Emette un messaggio di conferma quando il routing trova un provider funzionante
* dopo che almeno uno prioritario aveva fallito (wasFallback=true).
* Se wasFallback=false (provider normale trovato al primo tentativo) → no-op.
*
* @param providerKey - Chiave interna provider (es. "cerebras", "groq")
* @param model - Nome completo del modello selezionato
* @param wasFallback - true solo se _loopFailed > 0 nel routing loop
*/
export function narrateProviderSelected(
providerKey: string,
model: string,
wasFallback: boolean,
): void {
if (!_narratorCb || !wasFallback) return;
const name = _providerLabel(providerKey);
const modelShort = model.split("/").pop()?.replace(/-\d{4}-\d{2}-\d{2}/, "") ?? model;
_emit(`✓ Connesso su ${name} (${modelShort}) — rispondo subito`);
}
// ─── 4. Loop phase transitions → narrativa ────────────────────────────────────
/**
* Fasi del loop agente disponibili per la narrazione.
* Centralizza tutte le chiamate onStatus("...") delle fasi in apiErrorRecovery.ts.
*/
export type LoopPhase =
| "elaborating" // Elaboro risposta con i dati raccolti
| "direct" // Risposta diretta (no tool o dati aggiuntivi)
| "with-data" // Elaboro risposta con dati VFS/tool già presenti
| "data-ready" // Tool ha restituito dati, sintesi completata
| "retrying" // Riprovo con approccio o provider diverso
| "finalizing" // Finalizzo e verifico la risposta
| "searching"; // Cerco dati aggiuntivi prima di rispondere
const _LOOP_PHASE_LABELS: Record<LoopPhase, string> = {
elaborating: "Sto elaborando la risposta con i dati che ho…",
direct: "Rispondo direttamente…",
"with-data": "Ho i dati, sto scrivendo la risposta…",
"data-ready": "Ho tutto quello che serve ✓",
retrying: "Ci riprovo con un approccio diverso…",
finalizing: "Sto finalizzando…",
searching: "Sto cercando qualche informazione in più…",
};
/**
* Emette un messaggio narrativo per la fase corrente del loop agente.
* Sostituisce i singoli onStatus("...") hardcoded in apiErrorRecovery.ts.
*
* @param phase - Fase del loop (LoopPhase)
* @param onStatus - Callback esplicito (opzionale — fallback al cb registrato)
*/
/**
* Emette un messaggio di recovery dall'interno delle toolRecoveryChains.
* Usa il callback registrato dal loop corrente (setNarratorStatusCb).
* Zero dipendenze circolari — non importa toolRecoveryChains.
*/
export function narrateRecovery(msg: string): void {
_emit(msg);
}
export function narrateLoopPhase(
phase: LoopPhase,
onStatus?: (msg: string) => void,
): void {
const msg = _LOOP_PHASE_LABELS[phase];
if (!msg) return;
_emit(msg, onStatus);
}
|