/** * Success / retry classification for a PI RPC session. * Write() arguments in traces are never treated as deliverables. */ export function newSettleState() { return { retryFailed: false, providerTransient: false, lastErr: "", succeeded: false, }; } function messageField(ev, key) { const msg = ev && ev.message; if (!msg || typeof msg !== "object") { return ""; } const v = msg[key]; return typeof v === "string" ? v : ""; } export function isTransientText(s) { const t = String(s || "").toLowerCase(); return t.includes("connection error") || t.includes("terminated") || t.includes("auto_retry") || t.includes("econnreset") || t.includes("socket hang up"); } export function isPermanentReason(s) { const t = String(s || "").toLowerCase(); return t.includes("prompt_empty") || t.includes("unauthorized") || t.includes("治理"); } export function applyAgentEvent(state, ev) { if (!state || !ev || typeof ev !== "object") { return state; } const t = ev.type || ""; const stop = messageField(ev, "stopReason"); const msgErr = messageField(ev, "errorMessage") || (typeof ev.errorMessage === "string" ? ev.errorMessage : "") || (typeof ev.finalError === "string" ? ev.finalError : "") || (ev.error && typeof ev.error.message === "string" ? ev.error.message : ""); if (msgErr && isTransientText(msgErr)) { state.providerTransient = true; state.lastErr = msgErr; } if (stop === "error") { state.providerTransient = true; if (msgErr) { state.lastErr = msgErr; } } if (t === "auto_retry_start") { state.providerTransient = true; if (ev.errorMessage) { state.lastErr = String(ev.errorMessage); } } if (t === "auto_retry_end") { if (ev.success === false) { state.retryFailed = true; state.providerTransient = true; state.lastErr = ev.finalError || ev.errorMessage || state.lastErr || "auto_retry_failed"; } else if (ev.success === true) { // SDK in-process retry recovered. Drop this-round transient so a later // agent_settled can succeed; later events may set a new error again. state.retryFailed = false; state.providerTransient = false; state.lastErr = ""; } else { state.retryFailed = false; } } if (t === "agent_error") { const err = (ev.error && ev.error.message) || ev.message || "agent_error"; const text = typeof err === "string" ? err : "agent_error"; state.lastErr = text; if (isTransientText(text)) { state.providerTransient = true; } } if (t === "agent_settled") { // PI emits agent_settled even after exhausted SDK retries. state.succeeded = !state.retryFailed && !state.providerTransient; } return state; } export function classifyRunOutcome({ succeeded = false, retryFailed = false, providerTransient = false, lastErr = "", missing = [], } = {}) { const miss = Array.isArray(missing) ? missing.filter(Boolean) : []; if (isPermanentReason(lastErr)) { return { ok: false, retryable: false, fallback_reason: lastErr || "permanent_failure" }; } if (!succeeded) { const reason = lastErr || "agent_not_settled"; const retryable = (retryFailed || providerTransient || isTransientText(reason)) && !isPermanentReason(reason); return { ok: false, retryable, fallback_reason: reason }; } if (miss.length > 0) { return { ok: false, retryable: Boolean(retryFailed || providerTransient), fallback_reason: `missing target deliverable ${miss.join(",")}`, }; } return { ok: true, retryable: false, fallback_reason: "" }; }