import { redactLedgerString } from './ledger-safe-payload'; type ErrorLike = Record; export type TransportErrorDiagnostic = { name: string | null; message: string | null; code: string | null; errno: string | null; syscall: string | null; cause: TransportErrorDiagnostic | null; }; function asErrorLike(value: unknown): ErrorLike | null { return value !== null && typeof value === 'object' ? (value as ErrorLike) : null; } function safeString(value: unknown): string | null { if (typeof value !== 'string' && typeof value !== 'number') return null; return redactLedgerString(String(value))?.slice(0, 500) ?? null; } export function transportGatewayOriginForDiagnostic(url: string): string { try { return new URL(url).origin; } catch { return ''; } } /** * Make a small, secret-redacted transport diagnostic that can safely cross the * runner -> gateway -> durable run-log boundary. Keep stacks in process logs: * serializing them into run state leaks implementation details and is rarely * useful without the original error's code/errno/cause chain. */ export function describeTransportError( error: unknown, depth = 0, ): TransportErrorDiagnostic { const value = asErrorLike(error); const errorValue = error instanceof Error ? error : null; const name = safeString(errorValue?.name ?? value?.name) ?? (value ? 'Error' : null); const message = safeString(errorValue?.message ?? value?.message ?? error); const cause = value?.cause; return { name, message, code: safeString(value?.code), errno: safeString(value?.errno), syscall: safeString(value?.syscall), // A short cause chain is enough for fetch/undici errors and prevents a // pathological custom error graph from making durable traces unbounded. cause: cause !== undefined && depth < 2 ? describeTransportError(cause, depth + 1) : null, }; }