@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
765 lines (764 loc) • 31.7 kB
JavaScript
import { createHash, randomUUID } from "node:crypto";
import { constants as fsConstants, createReadStream } from "node:fs";
import { lstat, mkdir, open, readFile, readdir, realpath, rename, stat, unlink, } from "node:fs/promises";
import { homedir } from "node:os";
import { basename, dirname, isAbsolute, join, relative, resolve, sep, } from "node:path";
import { createInterface } from "node:readline";
import { gunzipSync } from "node:zlib";
import { prepareProxyBodyForLogging, redactProxyHeadersForLogging, } from "./requestLogger.js";
const DEBUG_FILE_PATTERN = /^proxy-debug-\d{4}-\d{2}-\d{2}\.jsonl$/;
const BUNDLE_KIND = "neurolink.proxy.replay-bundle";
const COMPARISON_KIND = "neurolink.proxy.replay-comparison";
const MAX_COMPRESSED_ARTIFACT_BYTES = 2 * 1024 * 1024;
const MAX_ARTIFACT_BYTES = 2 * 1024 * 1024;
const MAX_BUNDLE_BYTES = 16 * 1024 * 1024;
const MAX_DIRECT_RESPONSE_BYTES = 1024 * 1024;
const REDACTED_VALUE = "[REDACTED]";
const TRUSTED_CAPTURED_ENDPOINTS = new Set([
"https://api.anthropic.com/v1/messages?beta=true",
]);
const HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
const SENSITIVE_QUERY_NAME_PATTERN = /token|secret|key|password|credential|authorization/i;
const HOP_BY_HOP_HEADERS = new Set([
"connection",
"content-length",
"host",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailer",
"transfer-encoding",
"upgrade",
]);
export function isValidProxyReplayHeaderName(name) {
return HEADER_NAME_PATTERN.test(name);
}
function sha256(value) {
return createHash("sha256").update(value).digest("hex");
}
function stringValue(value) {
return typeof value === "string" && value.length > 0 ? value : null;
}
function finiteNumber(value) {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
function recordValue(value) {
return value && typeof value === "object" && !Array.isArray(value)
? value
: null;
}
function headersValue(value) {
const record = recordValue(value);
if (!record) {
return {};
}
return Object.fromEntries(Object.entries(record)
.filter((entry) => typeof entry[1] === "string")
.sort(([left], [right]) => left.localeCompare(right)));
}
function isStringArray(value) {
return (Array.isArray(value) && value.every((entry) => typeof entry === "string"));
}
function isNullableString(value) {
return value === null || typeof value === "string";
}
function isNullableNumber(value) {
return (value === null || (typeof value === "number" && Number.isFinite(value)));
}
function isNullableSha256(value) {
return (value === null || (typeof value === "string" && SHA256_PATTERN.test(value)));
}
function isHeaderRecord(value) {
const record = recordValue(value);
return (record !== null &&
Object.entries(record).every(([name, headerValue]) => isValidProxyReplayHeaderName(name) &&
typeof headerValue === "string" &&
!/[\r\n]/.test(headerValue)));
}
function isReplayCapture(value) {
const capture = recordValue(value);
const source = recordValue(capture?.source);
return Boolean(capture &&
typeof capture.timestamp === "string" &&
typeof capture.phase === "string" &&
isNullableNumber(capture.attempt) &&
isNullableString(capture.model) &&
(capture.stream === null || typeof capture.stream === "boolean") &&
isNullableString(capture.account) &&
isNullableString(capture.accountType) &&
isNullableNumber(capture.responseStatus) &&
isNullableNumber(capture.durationMs) &&
isNullableString(capture.contentType) &&
isHeaderRecord(capture.headers) &&
isNullableString(capture.body) &&
isNullableSha256(capture.bodySha256) &&
typeof capture.bodyTruncated === "boolean" &&
isNullableNumber(capture.observedBodyBytes) &&
(capture.metadata === null || recordValue(capture.metadata)) &&
source &&
typeof source.indexFile === "string" &&
Number.isInteger(source.indexLine) &&
Number(source.indexLine) >= 1 &&
isNullableString(source.artifactPath) &&
isStringArray(capture.issues));
}
function assertProxyReplayBundle(value) {
const bundle = recordValue(value);
const source = recordValue(bundle?.source);
const completeness = recordValue(bundle?.completeness);
const request = recordValue(bundle?.request);
const valid = Boolean(bundle?.kind === BUNDLE_KIND &&
bundle.schemaVersion === 1 &&
typeof bundle.requestId === "string" &&
bundle.requestId.length > 0 &&
Number.isInteger(bundle.selectedAttempt) &&
Number(bundle.selectedAttempt) >= 1 &&
source &&
typeof source.logsDirectory === "string" &&
isStringArray(source.indexFiles) &&
completeness &&
Number.isInteger(completeness.captures) &&
isStringArray(completeness.phasesPresent) &&
isStringArray(completeness.missingRequiredPhases) &&
Number.isInteger(completeness.truncatedCaptures) &&
Number.isInteger(completeness.artifactsWithIssues) &&
typeof completeness.replayable === "boolean" &&
isStringArray(completeness.blockers) &&
request &&
["POST", "PUT", "PATCH"].includes(String(request.method)) &&
isNullableString(request.url) &&
isHeaderRecord(request.headers) &&
isStringArray(request.requiredHeaderInputs) &&
request.requiredHeaderInputs.every((name) => isValidProxyReplayHeaderName(name)) &&
isNullableString(request.body) &&
isNullableSha256(request.bodySha256) &&
typeof request.bodyTruncated === "boolean" &&
isNullableString(request.contentType) &&
isNullableString(request.account) &&
isNullableString(request.accountType) &&
isNullableString(request.model) &&
(request.stream === null || typeof request.stream === "boolean") &&
Array.isArray(bundle.captures) &&
bundle.captures.every(isReplayCapture) &&
(bundle.capturedResponse === null ||
isReplayCapture(bundle.capturedResponse)));
if (!valid) {
throw new Error("Invalid or unsupported NeuroLink proxy replay bundle");
}
const typedBundle = value;
const captures = [
...typedBundle.captures,
...(typedBundle.capturedResponse ? [typedBundle.capturedResponse] : []),
];
const hashedBodies = [typedBundle.request, ...captures];
for (const capture of hashedBodies) {
if (capture.body !== null &&
capture.bodySha256 !== null &&
sha256(capture.body) !== capture.bodySha256) {
throw new Error("Proxy replay bundle contains a body hash mismatch");
}
}
if (stableJson(redactProxyHeadersForLogging(typedBundle.request.headers)) !==
stableJson(typedBundle.request.headers)) {
throw new Error("Proxy replay bundle contains an unredacted request header");
}
for (const capture of captures) {
if (stableJson(redactProxyHeadersForLogging(capture.headers)) !==
stableJson(capture.headers)) {
throw new Error("Proxy replay bundle contains an unredacted capture header");
}
if (capture.body !== null &&
prepareProxyBodyForLogging(capture.body).value !== capture.body) {
throw new Error("Proxy replay bundle contains an unredacted capture body");
}
}
if (typedBundle.request.body !== null &&
prepareProxyBodyForLogging(typedBundle.request.body).value !==
typedBundle.request.body) {
throw new Error("Proxy replay bundle contains an unredacted request body");
}
}
function isWithin(parent, candidate) {
const child = relative(parent, candidate);
return child === "" || (!child.startsWith(`..${sep}`) && child !== "..");
}
function stableValue(value) {
if (Array.isArray(value)) {
return value.map(stableValue);
}
if (value && typeof value === "object") {
return Object.fromEntries(Object.entries(value)
.sort(([left], [right]) => left.localeCompare(right))
.map(([key, child]) => [key, stableValue(child)]));
}
return value;
}
function stableJson(value) {
return JSON.stringify(stableValue(value));
}
export function serializeProxyReplayDocument(value) {
return `${JSON.stringify(stableValue(value), null, 2)}\n`;
}
async function discoverDebugFiles(logsDir) {
const entries = await readdir(logsDir, { withFileTypes: true }).catch((error) => {
throw new Error(`Unable to read proxy logs at ${logsDir}: ${error.message}`);
});
return entries
.filter((entry) => entry.isFile() && DEBUG_FILE_PATTERN.test(entry.name))
.map((entry) => join(logsDir, entry.name))
.sort();
}
async function findRequestIndexEntries(files, requestId) {
const matches = [];
const encodedRequestId = JSON.stringify(requestId);
for (const filePath of files) {
const lines = createInterface({
input: createReadStream(filePath, { encoding: "utf8" }),
crlfDelay: Infinity,
});
let lineNumber = 0;
for await (const line of lines) {
lineNumber += 1;
if (!line.includes(encodedRequestId)) {
continue;
}
let parsed;
try {
parsed = JSON.parse(line);
}
catch {
continue;
}
const index = recordValue(parsed);
if (index?.type === "body_capture" && index.requestId === requestId) {
matches.push({ index, filePath, line: lineNumber });
}
}
}
return matches;
}
async function loadArtifact(args) {
const bodyRoot = resolve(args.logsDir, "bodies");
const candidate = resolve(isAbsolute(args.bodyPath)
? args.bodyPath
: join(args.logsDir, args.bodyPath));
if (!isWithin(bodyRoot, candidate)) {
throw new Error(`Body artifact escapes the logs body directory: ${args.bodyPath}`);
}
const [bodyRootReal, candidateReal, candidateLstat] = await Promise.all([
realpath(bodyRoot),
realpath(candidate),
lstat(candidate),
]);
if (!isWithin(bodyRootReal, candidateReal) ||
candidateLstat.isSymbolicLink()) {
throw new Error(`Body artifact failed path-containment checks: ${args.bodyPath}`);
}
const handle = await open(candidateReal, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
let compressed;
try {
const info = await handle.stat();
if (!info.isFile()) {
throw new Error(`Body artifact is not a regular file: ${args.bodyPath}`);
}
if (info.size > MAX_COMPRESSED_ARTIFACT_BYTES) {
throw new Error(`Compressed body artifact exceeds the ${MAX_COMPRESSED_ARTIFACT_BYTES}-byte safety limit`);
}
compressed = await handle.readFile();
}
finally {
await handle.close();
}
const payload = gunzipSync(compressed, {
maxOutputLength: MAX_ARTIFACT_BYTES,
});
const artifact = recordValue(JSON.parse(payload.toString("utf8")));
if (!artifact) {
throw new Error(`Body artifact is not a JSON object: ${args.bodyPath}`);
}
if (artifact.requestId !== args.expectedRequestId ||
artifact.phase !== args.expectedPhase) {
throw new Error(`Body artifact identity does not match its debug index: ${args.bodyPath}`);
}
const body = typeof artifact.body === "string" ? artifact.body : null;
if (body !== null &&
args.expectedSha256 &&
sha256(body) !== args.expectedSha256) {
throw new Error(`Body artifact SHA-256 does not match its debug index: ${args.bodyPath}`);
}
return {
artifact,
relativePath: relative(args.logsDir, candidateReal).split(sep).join("/"),
};
}
async function materializeCapture(args) {
const phase = stringValue(args.index.phase) ?? "unknown";
const expectedSha256 = stringValue(args.index.bodySha256);
const issues = [];
let artifact = null;
let artifactPath = null;
const bodyPath = stringValue(args.index.bodyPath);
if (!bodyPath) {
issues.push("body_artifact_missing");
}
else {
try {
const loaded = await loadArtifact({
logsDir: args.logsDir,
bodyPath,
expectedRequestId: args.requestId,
expectedPhase: phase,
expectedSha256,
});
artifact = loaded.artifact;
artifactPath = loaded.relativePath;
for (const field of [
"timestamp",
"requestId",
"phase",
"model",
"stream",
"account",
"accountType",
"attempt",
"responseStatus",
"durationMs",
"contentType",
"headers",
"metadata",
]) {
if (stableJson(artifact[field]) !== stableJson(args.index[field])) {
throw new Error(`Body artifact ${field} does not match its debug index: ${bodyPath}`);
}
}
}
catch (error) {
if (error.code === "ENOENT") {
issues.push("body_artifact_missing");
}
else {
throw error;
}
}
}
const source = artifact ?? args.index;
const body = typeof artifact?.body === "string" ? artifact.body : null;
if (body !== null && !expectedSha256) {
issues.push("body_hash_missing");
}
return {
timestamp: stringValue(source.timestamp) ?? stringValue(args.index.timestamp) ?? "",
phase,
attempt: finiteNumber(source.attempt),
model: stringValue(source.model),
stream: typeof source.stream === "boolean" ? source.stream : null,
account: stringValue(source.account),
accountType: stringValue(source.accountType),
responseStatus: finiteNumber(source.responseStatus),
durationMs: finiteNumber(source.durationMs),
contentType: stringValue(source.contentType),
headers: headersValue(source.headers),
body,
bodySha256: body === null ? expectedSha256 : sha256(body),
bodyTruncated: args.index.bodyTruncated === true,
observedBodyBytes: finiteNumber(args.index.observedBodyBytes),
metadata: recordValue(source.metadata),
source: {
indexFile: basename(args.filePath),
indexLine: args.line,
artifactPath,
},
issues,
};
}
function captureSort(left, right) {
return (left.timestamp.localeCompare(right.timestamp) ||
(left.attempt ?? -1) - (right.attempt ?? -1) ||
left.phase.localeCompare(right.phase) ||
left.source.indexFile.localeCompare(right.source.indexFile) ||
left.source.indexLine - right.source.indexLine);
}
function findCapture(captures, phase, attempt) {
const matches = captures.filter((capture) => capture.phase === phase &&
(attempt === undefined || capture.attempt === attempt));
return matches.at(-1) ?? null;
}
export async function exportProxyReplayBundle(options) {
if (!options.requestId || options.requestId.length > 256) {
throw new Error("requestId must contain between 1 and 256 characters");
}
if (options.attempt !== undefined &&
(!Number.isInteger(options.attempt) || options.attempt < 1)) {
throw new Error("attempt must be a positive integer");
}
const logsDir = resolve(options.logsDir ?? join(homedir(), ".neurolink", "logs"));
const debugFiles = await discoverDebugFiles(logsDir);
const indexEntries = await findRequestIndexEntries(debugFiles, options.requestId);
if (indexEntries.length === 0) {
throw new Error(`No body captures found for request ${options.requestId}`);
}
const captures = (await Promise.all(indexEntries.map((entry) => materializeCapture({ logsDir, requestId: options.requestId, ...entry })))).sort(captureSort);
const upstreamAttempts = captures
.filter((capture) => capture.phase === "upstream_request" && capture.attempt !== null)
.map((capture) => capture.attempt);
const selectedAttempt = options.attempt ??
upstreamAttempts.reduce((highest, attempt) => Math.max(highest, attempt), Number.NEGATIVE_INFINITY);
if (!Number.isFinite(selectedAttempt)) {
throw new Error(`Request ${options.requestId} has no captured upstream attempt`);
}
const upstreamRequest = findCapture(captures, "upstream_request", selectedAttempt);
if (!upstreamRequest) {
throw new Error(`Request ${options.requestId} has no upstream_request capture for attempt ${selectedAttempt}`);
}
const capturedResponse = findCapture(captures, "upstream_response", selectedAttempt);
const phasesPresent = [
...new Set(captures.map((capture) => capture.phase)),
].sort();
const missingRequiredPhases = [
...(findCapture(captures, "client_request") ? [] : ["client_request"]),
...(upstreamRequest ? [] : ["upstream_request"]),
...(capturedResponse ? [] : ["upstream_response"]),
...(findCapture(captures, "client_response") ? [] : ["client_response"]),
];
const requiredHeaderInputs = Object.entries(upstreamRequest.headers)
.filter(([, value]) => value === REDACTED_VALUE)
.map(([name]) => name.toLowerCase())
.sort();
const metadata = upstreamRequest.metadata ?? {};
const url = stringValue(metadata.upstreamUrl);
const method = (stringValue(metadata.upstreamMethod) ?? "POST").toUpperCase();
const blockers = [
...missingRequiredPhases.map((phase) => `missing_phase:${phase}`),
...(url ? [] : ["missing_upstream_url"]),
...(upstreamRequest.body === null ? ["missing_upstream_request_body"] : []),
...(upstreamRequest.bodyTruncated
? ["upstream_request_body_truncated"]
: []),
...(upstreamRequest.body?.includes(REDACTED_VALUE)
? ["upstream_request_body_contains_redactions"]
: []),
...captures.flatMap((capture) => capture.issues.map((issue) => `${capture.phase}:${capture.attempt ?? "none"}:${issue}`)),
].sort();
return {
schemaVersion: 1,
kind: BUNDLE_KIND,
requestId: options.requestId,
selectedAttempt,
source: {
logsDirectory: basename(logsDir),
indexFiles: [
...new Set(captures.map((capture) => capture.source.indexFile)),
].sort(),
},
completeness: {
captures: captures.length,
phasesPresent,
missingRequiredPhases,
truncatedCaptures: captures.filter((capture) => capture.bodyTruncated)
.length,
artifactsWithIssues: captures.filter((capture) => capture.issues.length > 0).length,
replayable: blockers.length === 0,
blockers,
},
request: {
method,
url,
headers: upstreamRequest.headers,
requiredHeaderInputs,
body: upstreamRequest.body,
bodySha256: upstreamRequest.bodySha256,
bodyTruncated: upstreamRequest.bodyTruncated,
contentType: upstreamRequest.contentType,
account: upstreamRequest.account,
accountType: upstreamRequest.accountType,
model: upstreamRequest.model,
stream: upstreamRequest.stream,
},
capturedResponse,
captures,
};
}
export async function writeProxyReplayDocument(outputPath, document) {
const resolvedPath = resolve(outputPath);
const outputDirectory = dirname(resolvedPath);
await mkdir(outputDirectory, { recursive: true, mode: 0o700 });
const existing = await lstat(resolvedPath).catch((error) => {
if (error.code === "ENOENT") {
return null;
}
throw error;
});
if (existing?.isSymbolicLink()) {
throw new Error(`Refusing to replace symlink output path: ${resolvedPath}`);
}
const serialized = serializeProxyReplayDocument(document);
const documentBytes = Buffer.byteLength(serialized, "utf8");
if (documentBytes > MAX_BUNDLE_BYTES) {
throw new Error(`Replay document exceeds the ${MAX_BUNDLE_BYTES}-byte safety limit`);
}
const temporaryPath = join(outputDirectory, `.${basename(resolvedPath)}.${process.pid}.${randomUUID()}.tmp`);
const handle = await open(temporaryPath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 0o600);
try {
try {
await handle.writeFile(serialized);
await handle.chmod(0o600);
await handle.sync();
}
finally {
await handle.close();
}
await rename(temporaryPath, resolvedPath);
}
catch (error) {
await unlink(temporaryPath).catch(() => undefined);
throw error;
}
return { path: resolvedPath, sha256: sha256(serialized) };
}
export async function readProxyReplayBundle(bundlePath) {
const resolvedPath = resolve(bundlePath);
const info = await stat(resolvedPath);
if (!info.isFile() || info.size > MAX_BUNDLE_BYTES) {
throw new Error(`Replay bundle must be a regular file no larger than ${MAX_BUNDLE_BYTES} bytes`);
}
const parsed = JSON.parse(await readFile(resolvedPath, "utf8"));
assertProxyReplayBundle(parsed);
return parsed;
}
function normalizedContentType(value) {
return value?.split(";", 1)[0]?.trim().toLowerCase() || null;
}
function jsonShape(body) {
let parsed;
try {
parsed = JSON.parse(body);
}
catch {
return null;
}
const paths = [];
const walk = (value, path, depth) => {
if (paths.length >= 2_000 || depth > 20) {
return;
}
if (Array.isArray(value)) {
paths.push(`${path}:array`);
value
.slice(0, 20)
.forEach((child, index) => walk(child, `${path}/${index}`, depth + 1));
return;
}
if (value && typeof value === "object") {
paths.push(`${path}:object`);
for (const [key, child] of Object.entries(value).sort(([left], [right]) => left.localeCompare(right))) {
walk(child, `${path}/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`, depth + 1);
}
return;
}
paths.push(`${path}:${value === null ? "null" : typeof value}`);
};
walk(parsed, "", 0);
return paths;
}
function validateDirectEndpoint(value, explicitOverride) {
if (!value) {
throw new Error("Replay bundle has no upstream URL; provide an explicit URL override");
}
const endpoint = new URL(value);
if (endpoint.username || endpoint.password || endpoint.hash) {
throw new Error("Replay endpoint must not contain credentials or a fragment");
}
for (const name of endpoint.searchParams.keys()) {
if (SENSITIVE_QUERY_NAME_PATTERN.test(name)) {
throw new Error(`Replay endpoint must not contain a sensitive query parameter: ${name}`);
}
}
const loopback = ["127.0.0.1", "::1", "[::1]", "localhost"].includes(endpoint.hostname);
if (endpoint.protocol !== "https:" &&
!(endpoint.protocol === "http:" && loopback)) {
throw new Error("Replay endpoint must use HTTPS; HTTP is allowed only for loopback testing");
}
if (!explicitOverride &&
!TRUSTED_CAPTURED_ENDPOINTS.has(endpoint.toString())) {
throw new Error("Replay bundle contains an untrusted endpoint; pass an explicit URL override to authorize it");
}
return endpoint;
}
async function readBoundedResponse(response) {
if (!response.body) {
return { body: "", observedBytes: 0, truncated: false };
}
const reader = response.body.getReader();
const chunks = [];
let storedBytes = 0;
let observedBytes = 0;
let truncated = false;
try {
while (true) {
const next = await reader.read();
if (next.done) {
break;
}
observedBytes += next.value.byteLength;
if (storedBytes < MAX_DIRECT_RESPONSE_BYTES) {
const remaining = MAX_DIRECT_RESPONSE_BYTES - storedBytes;
chunks.push(next.value.slice(0, remaining));
storedBytes += Math.min(remaining, next.value.byteLength);
}
if (observedBytes > MAX_DIRECT_RESPONSE_BYTES) {
truncated = true;
await reader
.cancel("bounded replay comparison capture")
.catch(() => undefined);
break;
}
}
}
finally {
reader.releaseLock();
}
return {
body: Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))).toString("utf8"),
observedBytes,
truncated,
};
}
export async function compareProxyReplayBundle(bundle, options) {
assertProxyReplayBundle(bundle);
if (!options.execute) {
throw new Error("Direct replay is disabled unless execute=true is explicitly provided");
}
if (!["POST", "PUT", "PATCH"].includes(bundle.request.method)) {
throw new Error(`Unsupported replay request method: ${bundle.request.method}`);
}
const requestBody = options.bodyOverride ?? bundle.request.body;
if (requestBody === null) {
throw new Error("Replay request body is missing; provide a body override");
}
if (options.bodyOverride === undefined &&
(bundle.request.bodyTruncated || requestBody.includes(REDACTED_VALUE))) {
throw new Error("Captured replay body is truncated or redacted; provide a complete body override");
}
if (options.bodyOverride === undefined &&
bundle.request.bodySha256 === null) {
throw new Error("Captured replay body has no verified hash; provide a complete body override");
}
const endpoint = validateDirectEndpoint(options.urlOverride ?? bundle.request.url ?? "", options.urlOverride !== undefined);
const suppliedHeaders = Object.fromEntries(Object.entries(options.headerValues ?? {}).map(([name, value]) => [
name.toLowerCase(),
value,
]));
const invalidHeaderNames = Object.keys(suppliedHeaders).filter((name) => !isValidProxyReplayHeaderName(name));
if (invalidHeaderNames.length > 0) {
throw new Error(`Invalid replay header names: ${invalidHeaderNames.join(", ")}`);
}
const requiredHeaderInputs = [
...new Set([
...bundle.request.requiredHeaderInputs.map((name) => name.toLowerCase()),
...Object.entries(bundle.request.headers)
.filter(([, value]) => value === REDACTED_VALUE)
.map(([name]) => name.toLowerCase()),
]),
].sort();
const missingHeaderValues = requiredHeaderInputs.filter((name) => !suppliedHeaders[name]);
if (missingHeaderValues.length > 0) {
throw new Error(`Missing environment-backed values for redacted headers: ${missingHeaderValues.join(", ")}`);
}
const requestHeaders = {};
for (const [name, capturedValue] of Object.entries(bundle.request.headers)) {
const lower = name.toLowerCase();
if (HOP_BY_HOP_HEADERS.has(lower)) {
continue;
}
requestHeaders[lower] =
capturedValue === REDACTED_VALUE
? suppliedHeaders[lower]
: capturedValue;
}
const timeoutMs = options.timeoutMs ?? 120_000;
if (!Number.isFinite(timeoutMs) || timeoutMs < 1_000 || timeoutMs > 600_000) {
throw new Error("Replay timeout must be between 1000 and 600000 milliseconds");
}
const now = options.now ?? Date.now;
const startedAt = now();
const response = await (options.fetchImpl ?? fetch)(endpoint, {
method: bundle.request.method,
headers: requestHeaders,
body: requestBody,
redirect: "manual",
signal: AbortSignal.timeout(timeoutMs),
});
const headersAt = now();
const rawResponseHeaders = Object.fromEntries(response.headers.entries());
const directCapture = await readBoundedResponse(response);
const endedAt = now();
const preparedBody = prepareProxyBodyForLogging(directCapture.body);
const redactedBody = preparedBody.value ?? "";
const captured = bundle.capturedResponse;
const capturedShape = captured?.body ? jsonShape(captured.body) : null;
const directShape = jsonShape(redactedBody);
const capturedContentType = normalizedContentType(captured?.contentType ?? null);
const directContentType = normalizedContentType(response.headers.get("content-type"));
return {
schemaVersion: 1,
kind: COMPARISON_KIND,
requestId: bundle.requestId,
selectedAttempt: bundle.selectedAttempt,
endpoint: endpoint.toString(),
request: {
method: bundle.request.method,
headers: redactProxyHeadersForLogging(requestHeaders) ?? {},
bodySha256: sha256(requestBody),
bodyBytes: Buffer.byteLength(requestBody, "utf8"),
usedBodyOverride: options.bodyOverride !== undefined,
},
captured: captured
? {
status: captured.responseStatus,
contentType: captured.contentType,
bodySha256: captured.bodySha256,
bodyBytes: captured.observedBodyBytes,
bodyTruncated: captured.bodyTruncated,
jsonShape: capturedShape,
}
: null,
direct: {
status: response.status,
headers: redactProxyHeadersForLogging(rawResponseHeaders) ?? {},
contentType: response.headers.get("content-type"),
body: redactedBody,
bodySha256: sha256(redactedBody),
observedBodyBytes: directCapture.observedBytes,
storedBodyBytes: Buffer.byteLength(redactedBody, "utf8"),
bodyTruncated: directCapture.truncated || preparedBody.truncated,
timeToHeadersMs: headersAt - startedAt,
totalMs: endedAt - startedAt,
jsonShape: directShape,
},
comparison: {
statusMatches: captured && captured.responseStatus !== null
? captured.responseStatus === response.status
: null,
contentTypeMatches: captured
? capturedContentType !== null && directContentType !== null
? capturedContentType === directContentType
: null
: null,
bodyHashMatches: captured?.bodySha256 &&
!captured.bodyTruncated &&
!directCapture.truncated &&
!preparedBody.truncated
? captured.bodySha256 === sha256(redactedBody)
: null,
jsonShapeMatches: capturedShape &&
directShape &&
!captured?.bodyTruncated &&
!directCapture.truncated &&
!preparedBody.truncated
? JSON.stringify(capturedShape) === JSON.stringify(directShape)
: null,
},
};
}