const sensitiveKey = /token|secret|password|api.?key|authorization/iu function redactValue( value: unknown, seen: WeakSet, depth = 0 ): unknown { if (depth > 8) { return '[TRUNCATED]' } if (typeof value === 'string') { return redactSensitiveText(value) } if (!value || typeof value !== 'object') { return value } if (seen.has(value)) { return '[CIRCULAR]' } seen.add(value) if (Array.isArray(value)) { return value .slice(0, 100) .map((item) => redactValue(item, seen, depth + 1)) } return Object.fromEntries( Object.entries(value) .slice(0, 100) .map(([key, item]) => [ key, sensitiveKey.test(key) ? '[REDACTED]' : redactValue(item, seen, depth + 1) ]) ) } export function redactSensitiveText(value: string): string { return value .replace( /\bAuthorization\b(\s*[:=]\s*)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\r\n,;}]+)/giu, 'Authorization$1[REDACTED]' ) .replace(/\bBearer\s+\S+/giu, 'Bearer [REDACTED]') .replace( /(["']?)(api[-_ ]?key|token|secret|password|authorization)\1(\s*[:=]\s*)"[^"\r\n]*"/giu, '$1$2$1$3"[REDACTED]"' ) .replace( /(["']?)(api[-_ ]?key|token|secret|password|authorization)\1(\s*[:=]\s*)'[^'\r\n]*'/giu, "$1$2$1$3'[REDACTED]'" ) .replace( /\b(api[-_ ]?key|token|secret|password|authorization)\b(\s*[:=]\s*|\s+)(["']?)[^\s"',}]+/giu, '$1$2[REDACTED]' ) } export function safeToolErrorDetail( value: unknown, maximum = 2_000 ): string | undefined { if (!Number.isSafeInteger(maximum) || maximum < 1) { return undefined } const parts: string[] = [] let remaining = maximum const seen = new WeakSet() const append = (value: string): void => { const text = [...value] .filter((character) => { const code = character.charCodeAt(0) return ( code === 9 || code === 10 || code === 13 || (code > 31 && code !== 127) ) }) .join('') .trim() if (!text || remaining <= 0) { return } const separator = parts.length > 0 ? '\n' : '' const available = Math.max(0, remaining - separator.length) if (available === 0) { return } const bounded = text.slice(0, available) parts.push(`${separator}${bounded}`) remaining -= separator.length + bounded.length } const collect = (candidate: unknown, depth = 0): void => { if (remaining <= 0 || depth > 4 || candidate === undefined) { return } if (typeof candidate === 'string') { const boundedCandidate = candidate.slice( 0, Math.min(candidate.length, remaining * 4) ) append(boundedCandidate) return } if (!candidate || typeof candidate !== 'object') { return } if (seen.has(candidate)) { return } seen.add(candidate) if (Array.isArray(candidate)) { for (const item of candidate.slice(0, 20)) { collect(item, depth + 1) } return } const record = candidate as Record collect(record.message, depth + 1) for (const key of [ 'code', 'errno', 'syscall', 'hostname', 'address', 'port', 'status', 'statusCode' ]) { const metadata = record[key] if ( typeof metadata === 'string' || typeof metadata === 'number' ) { append(`${key}: ${metadata}`) } } if (record.cause !== undefined) { append('cause:') collect(record.cause, depth + 1) } for (const key of [ 'content', 'error', 'errors', 'stderr', 'detail', 'data' ]) { collect(record[key], depth + 1) } } collect(value) return parts.join('').trim() || undefined } export function safeToolArgumentSummary( toolArguments: Record, preview?: unknown[], maximum = 1_000 ): string { const previewText = preview ?.slice(0, 100) .flatMap((item) => { if (!item || typeof item !== 'object') { return [] } const content = (item as Record).content return typeof content === 'string' ? [content] : [] }) .join(' ') .trim() if (previewText) { return redactSensitiveText(previewText).slice(0, maximum) } return JSON.stringify( redactValue(toolArguments, new WeakSet()) ).slice(0, maximum) } export function boundedToolDetail( value: unknown, maximum: number ): string | undefined { if (!Number.isSafeInteger(maximum) || maximum < 1 || value === undefined) { return undefined } let text: string | undefined if (typeof value === 'string') { text = value } else { try { text = JSON.stringify(value, null, 2) } catch { return undefined } } return text ? text.slice(0, maximum) : undefined }