UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

181 lines (180 loc) 6.74 kB
import { _ as parseStrictFiniteNumber, c as asFiniteNumberInRange } from "./number-coercion-CJQ8TR--.js"; import { c as isRecord } from "./utils-CCC-BEJH.js"; import "./number-runtime-DBLVDypr.js"; import "./string-coerce-runtime-CEGJWkQ_.js"; //#region extensions/msteams/src/errors.ts const MAX_SAFE_RETRY_AFTER_SECONDS = Number.MAX_SAFE_INTEGER / 1e3; function formatUnknownError(err) { if (err instanceof Error) return err.message; if (typeof err === "string") return err; if (err === null) return "null"; if (err === void 0) return "undefined"; if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") return String(err); if (typeof err === "symbol") return err.description ?? err.toString(); if (typeof err === "function") return err.name ? `[function ${err.name}]` : "[function]"; try { return JSON.stringify(err) ?? "unknown error"; } catch { return "unknown error"; } } function extractStatusCode(err) { if (!isRecord(err)) return null; const parseStatusCode = (value) => { if (typeof value === "number") return Number.isInteger(value) && value >= 100 && value <= 599 ? value : null; if (typeof value === "string") { const trimmed = value.trim(); if (!/^\d{3}$/.test(trimmed)) return null; const parsed = Number(trimmed); return parsed >= 100 && parsed <= 599 ? parsed : null; } return null; }; const directStatus = parseStatusCode(err.statusCode ?? err.status); if (directStatus !== null) return directStatus; const response = err.response; if (isRecord(response)) { const responseStatus = parseStatusCode(response.status); if (responseStatus !== null) return responseStatus; } return null; } function extractErrorCode(err) { if (!isRecord(err)) return null; const direct = err.code; if (typeof direct === "string" && direct.trim()) return direct; const response = err.response; if (!isRecord(response)) return null; const body = response.body; if (isRecord(body)) { const error = body.error; if (isRecord(error) && typeof error.code === "string" && error.code.trim()) return error.code; } return null; } function extractRetryAfterMs(err) { if (!isRecord(err)) return null; const directMs = asFiniteNumberInRange(err.retryAfterMs ?? err.retry_after_ms, { min: 0, max: Number.MAX_SAFE_INTEGER }); if (directMs !== void 0) return directMs; const retryAfter = err.retryAfter ?? err.retry_after; const retryAfterSeconds = asFiniteNumberInRange(retryAfter, { min: 0, max: MAX_SAFE_RETRY_AFTER_SECONDS }); if (retryAfterSeconds !== void 0) return retryAfterSeconds * 1e3; if (typeof retryAfter === "string") { const parsed = parseNonNegativeRetryAfterSeconds(retryAfter); if (parsed !== void 0) return parsed * 1e3; } const response = err.response; if (!isRecord(response)) return null; const headers = response.headers; if (!headers) return null; if (isRecord(headers)) { const raw = headers["retry-after"] ?? headers["Retry-After"]; if (typeof raw === "string") { const parsed = parseNonNegativeRetryAfterSeconds(raw); if (parsed !== void 0) return parsed * 1e3; } } if (typeof headers === "object" && headers !== null && "get" in headers && typeof headers.get === "function") { const raw = headers.get("retry-after"); if (raw) { const parsed = parseNonNegativeRetryAfterSeconds(raw); if (parsed !== void 0) return parsed * 1e3; } } return null; } function parseNonNegativeRetryAfterSeconds(raw) { const trimmed = raw.trim(); if (!/^\d+(?:\.\d+)?$/.test(trimmed)) return; return asFiniteNumberInRange(parseStrictFiniteNumber(trimmed), { min: 0, max: MAX_SAFE_RETRY_AFTER_SECONDS }); } /** * Classify outbound send errors for safe retries and actionable logs. * * Important: We only mark errors as retryable when we have an explicit HTTP * status code that indicates the message was not accepted (e.g. 429, 5xx). * For transport-level errors where delivery is ambiguous, we prefer to avoid * retries to reduce the chance of duplicate posts. */ function classifyMSTeamsSendError(err) { const statusCode = extractStatusCode(err); const retryAfterMs = extractRetryAfterMs(err); const errorCode = extractErrorCode(err) ?? void 0; if (statusCode === 401) return { kind: "auth", statusCode, errorCode }; if (statusCode === 403) { if (errorCode === "ContentStreamNotAllowed") return { kind: "permanent", statusCode, errorCode }; return { kind: "auth", statusCode, errorCode }; } if (statusCode === 429) return { kind: "throttled", statusCode, retryAfterMs: retryAfterMs ?? void 0, errorCode }; if (statusCode === 408 || statusCode != null && statusCode >= 500) return { kind: "transient", statusCode, retryAfterMs: retryAfterMs ?? void 0, errorCode }; if (statusCode != null && statusCode >= 400) return { kind: "permanent", statusCode, errorCode }; if (statusCode == null) { const networkCode = isRecord(err) && typeof err.code === "string" ? err.code : null; if (networkCode === "ECONNREFUSED" || networkCode === "ENOTFOUND" || networkCode === "EHOSTUNREACH" || networkCode === "ETIMEDOUT" || networkCode === "ECONNRESET") return { kind: "network", errorCode: networkCode }; } return { kind: "unknown", statusCode: statusCode ?? void 0, retryAfterMs: retryAfterMs ?? void 0, errorCode }; } /** * Detect whether an error is caused by a revoked Proxy. * * The Bot Framework SDK wraps TurnContext in a Proxy that is revoked once the * turn handler returns. Any later access (e.g. from a debounced callback) * throws a TypeError whose message contains the distinctive "proxy that has * been revoked" string. */ function isRevokedProxyError(err) { if (!(err instanceof TypeError)) return false; return /proxy that has been revoked/i.test(err.message); } function formatMSTeamsSendErrorHint(classification) { if (classification.kind === "auth") return "check msteams appId/appPassword/tenantId (or env vars MSTEAMS_APP_ID/MSTEAMS_APP_PASSWORD/MSTEAMS_TENANT_ID)"; if (classification.errorCode === "ContentStreamNotAllowed") return "Teams expired the content stream; stop streaming earlier and fall back to normal message delivery"; if (classification.kind === "throttled") return "Teams throttled the bot; backing off may help"; if (classification.kind === "transient") return "transient Teams/Bot Framework error; retry may succeed"; if (classification.kind === "network") return "transport-level failure sending reply to Teams Bot Connector (smba.trafficmanager.net) — check egress firewall rules allow outbound HTTPS to smba.trafficmanager.net"; } //#endregion export { isRevokedProxyError as i, formatMSTeamsSendErrorHint as n, formatUnknownError as r, classifyMSTeamsSendError as t };