function createDebugContentLog({ previewDebugBody, DEBUG_BODY_LIMIT, debugState, renderDebugPanel }) { function logRequestStart(details) { const entry = { id: debugState.requestLog.length + 1, name: details.name, method: details.method, url: details.url, headers: details.headers, status: "pending", startedAt: new Date(), completedAt: null, durationMs: null, httpStatus: null, statusText: "", requestHeaders: details.headers, requestBody: details.body || "", metadata: details.metadata || {}, responseHeaders: {}, responseBody: "", responseBodyTruncated: false, error: "" }; debugState.requestLog.push(entry); renderDebugPanel(); return entry; } function logRequestSuccess(entry, response, responseBody) { entry.status = response.ok ? "success" : "failed"; entry.completedAt = new Date(); entry.durationMs = entry.completedAt.getTime() - entry.startedAt.getTime(); entry.httpStatus = response.status; entry.statusText = response.statusText || ""; entry.responseHeaders = headersToObject(response.headers); entry.responseBody = responseBody.value; entry.responseBodyTruncated = responseBody.truncated; renderDebugPanel(); } function logRequestFailure(entry, errorMessage) { entry.status = "failed"; entry.completedAt = new Date(); entry.durationMs = entry.completedAt.getTime() - entry.startedAt.getTime(); entry.error = errorMessage; renderDebugPanel(); } function appendBackgroundRequestLog(entries) { if (!Array.isArray(entries) || entries.length === 0) { return; } entries.forEach((entry) => { const existing = entry.debugKey && debugState.requestLog.find(item => item.debugKey === entry.debugKey); if (existing && existing.status !== "pending" && entry.status === "pending") return; const normalized = { id: existing ? existing.id : debugState.requestLog.length + 1, debugKey: entry.debugKey, name: entry.name || "backgroundRequest", method: entry.method || "GET", url: entry.url || "", headers: entry.requestHeaders || {}, status: entry.status || "failed", startedAt: new Date(entry.startedAt), completedAt: entry.completedAt ? new Date(entry.completedAt) : null, durationMs: entry.durationMs, httpStatus: entry.httpStatus, statusText: entry.statusText || "", requestHeaders: entry.requestHeaders || {}, requestBody: entry.requestBody || "", metadata: entry.metadata || { executionContext: "background" }, responseHeaders: entry.responseHeaders || {}, responseBody: entry.responseBody || "", responseBodyTruncated: Boolean(entry.responseBodyTruncated), error: entry.error || "" }; if (existing) Object.assign(existing, normalized); else debugState.requestLog.push(normalized); }); renderDebugPanel(); } async function readResponseBodyForDebug(response) { try { const text = await response.clone().text(); return createDebugBody(text); } catch (error) { return { value: `Unable to read response body: ${error.message || "unknown error"}`, truncated: false }; } } function createDebugBody(text) { return previewDebugBody(text, DEBUG_BODY_LIMIT); } function headersToObject(headers) { const headerMap = {}; headers.forEach((value, key) => { headerMap[key] = value; }); return headerMap; } function formatHeaderBlock(headers) { const entries = Object.entries(headers); if (entries.length === 0) { return "(none)"; } return entries .map(([key, value]) => `${key}: ${debugState.debugSecretsVisible || !isSensitiveDebugKey(key) ? value : formatHiddenDebugValue(value)}`) .join("\n"); } function formatDebugBody(body) { const text = body || "(none)"; try { const normalized = String(text).replace(/^\uFEFF/, "").trimStart() .replace(/^\)\]\}',?\s*/, "") .replace(/^while\s*\(1\);\s*/, ""); let value = JSON.parse(normalized); // Some services serialize their JSON document inside a JSON string. for (let depth = 0; depth < 2 && typeof value === "string" && /^[\s]*[\[{]/.test(value); depth += 1) { try { value = JSON.parse(value); } catch { break; } } return JSON.stringify(debugState.debugSecretsVisible ? value : maskSensitiveDebugValue(value), null, 2); } catch (error) { return text; } } function maskSensitiveDebugValue(value, parentKey = "") { if (Array.isArray(value)) { return value.map((item) => maskSensitiveDebugValue(item, parentKey)); } if (!value || typeof value !== "object") { return value; } return Object.fromEntries(Object.entries(value).map(([key, child]) => { const isSensitive = isSensitiveDebugKey(key) || (key === "value" && /uiaasHeader|uiassHeader/i.test(parentKey)); return [key, isSensitive ? formatHiddenDebugValue(child) : maskSensitiveDebugValue(child, key)]; })); } function isSensitiveDebugKey(key) { return /authorization|cookie|password|secret|(^|[_-])token($|[_-])|token$/i.test(key); } function formatHiddenDebugValue(value) { if (value === null || value === undefined || value === "") { return "(missing)"; } return `(present, ${String(value).length} characters)`; } function formatMetadataBlock(metadata) { const entries = Object.entries(metadata); if (entries.length === 0) { return "(none)"; } return entries .map(([key, value]) => `${key}: ${value || "(none)"}`) .join("\n"); } function maskToken(token) { if (token.length <= 10) { return "(present)"; } return `${token.slice(0, 4)}...${token.slice(-4)}`; } return { logRequestStart, logRequestSuccess, logRequestFailure, appendBackgroundRequestLog, readResponseBodyForDebug, createDebugBody, headersToObject, formatHeaderBlock, formatDebugBody, maskSensitiveDebugValue, isSensitiveDebugKey, formatHiddenDebugValue, formatMetadataBlock, maskToken }; }