@juspay/neurolink 10.0.1 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,19 @@ export declare function logRequest(entry: RequestLogEntry): Promise<void>;
15
15
  */
16
16
  export declare function logRequestAttempt(entry: RequestAttemptLogEntry): Promise<void>;
17
17
  export declare function getLogDir(): string | null;
18
+ /** Shared redaction used by offline replay exports and direct comparisons. */
19
+ export declare function redactProxyHeadersForLogging(headers: Record<string, string> | undefined): Record<string, string> | undefined;
20
+ /**
21
+ * Apply the same bounded body redaction used by persisted proxy captures.
22
+ * `value` and `bytes` are omitted only when the input is null or undefined.
23
+ * This performs serialization immediately, so callers must keep it off proxy
24
+ * hot paths unless body processing has already been explicitly requested.
25
+ */
26
+ export declare function prepareProxyBodyForLogging(body: unknown): {
27
+ value?: string;
28
+ bytes?: number;
29
+ truncated: boolean;
30
+ };
18
31
  export declare function logBodyCapture(entry: ProxyBodyCaptureEntry): Promise<void>;
19
32
  /**
20
33
  * Log the FULL raw request and response for debugging.
@@ -330,6 +330,19 @@ function prepareRedactedBody(body) {
330
330
  }
331
331
  return truncateUtf8String(redacted, MAX_CAPTURED_BODY_BYTES);
332
332
  }
333
+ /** Shared redaction used by offline replay exports and direct comparisons. */
334
+ export function redactProxyHeadersForLogging(headers) {
335
+ return redactHeaders(headers);
336
+ }
337
+ /**
338
+ * Apply the same bounded body redaction used by persisted proxy captures.
339
+ * `value` and `bytes` are omitted only when the input is null or undefined.
340
+ * This performs serialization immediately, so callers must keep it off proxy
341
+ * hot paths unless body processing has already been explicitly requested.
342
+ */
343
+ export function prepareProxyBodyForLogging(body) {
344
+ return prepareRedactedBody(body);
345
+ }
333
346
  function collectManagedLogFiles(rootDir) {
334
347
  const managedFiles = [];
335
348
  const walk = (directory) => {
@@ -167,6 +167,7 @@ declare function handleAnthropicAuthRetry(args: {
167
167
  bodyStr: string;
168
168
  sessionId?: string;
169
169
  };
170
+ url: string;
170
171
  enabledAccounts: ProxyPassthroughAccount[];
171
172
  orderedAccounts: ProxyPassthroughAccount[];
172
173
  tracer?: ProxyTracer;
@@ -1011,6 +1011,10 @@ async function handleClaudePassthroughRequest(args) {
1011
1011
  account: "passthrough",
1012
1012
  accountType: "passthrough",
1013
1013
  attempt: 1,
1014
+ metadata: {
1015
+ upstreamMethod: "POST",
1016
+ upstreamUrl: "https://api.anthropic.com/v1/messages?beta=true",
1017
+ },
1014
1018
  });
1015
1019
  let response;
1016
1020
  try {
@@ -2914,7 +2918,7 @@ async function handleAnthropicSuccessfulRetryResponse(args) {
2914
2918
  return retryJson;
2915
2919
  }
2916
2920
  async function handleAnthropicAuthRetry(args) {
2917
- const { ctx, body, account, accountState, headers, buildUpstreamBody, enabledAccounts, orderedAccounts, tracer, requestStartTime, allocateAttemptNumber, upstreamSpan, logAttempt, logProxyBody, logFinalRequest, lastError, authFailureMessage, sawRateLimit, sawTransientFailure, sawNetworkError, } = args;
2921
+ const { ctx, body, account, accountState, headers, buildUpstreamBody, url, enabledAccounts, orderedAccounts, tracer, requestStartTime, allocateAttemptNumber, upstreamSpan, logAttempt, logProxyBody, logFinalRequest, lastError, authFailureMessage, sawRateLimit, sawTransientFailure, sawNetworkError, } = args;
2918
2922
  recordAttemptError(account.label, account.type, 401);
2919
2923
  logAttempt(401, "authentication_error", "received 401 from Anthropic", {
2920
2924
  retryable: true,
@@ -2969,9 +2973,10 @@ async function handleAnthropicAuthRetry(args) {
2969
2973
  account: account.label,
2970
2974
  accountType: account.type,
2971
2975
  attempt: retryAttemptNumber,
2976
+ metadata: { upstreamMethod: "POST", upstreamUrl: url },
2972
2977
  });
2973
2978
  try {
2974
- const retryResp = await fetch("https://api.anthropic.com/v1/messages?beta=true", {
2979
+ const retryResp = await fetch(url, {
2975
2980
  method: "POST",
2976
2981
  headers,
2977
2982
  body: retryBodyStr,
@@ -3692,6 +3697,7 @@ async function prepareAnthropicAccountAttempt(args) {
3692
3697
  account: account.label,
3693
3698
  accountType: account.type,
3694
3699
  attempt: attemptNumber,
3700
+ metadata: { upstreamMethod: "POST", upstreamUrl: url },
3695
3701
  });
3696
3702
  return {
3697
3703
  continueLoop: false,
@@ -4087,6 +4093,7 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4087
4093
  accountState,
4088
4094
  headers: preparedAttempt.headers,
4089
4095
  buildUpstreamBody: preparedAttempt.buildUpstreamBody,
4096
+ url,
4090
4097
  enabledAccounts,
4091
4098
  orderedAccounts,
4092
4099
  tracer,
@@ -798,6 +798,22 @@ export type ProxyAnalyzeArgs = {
798
798
  format?: "text" | "json";
799
799
  quiet?: boolean;
800
800
  };
801
+ /** Arguments accepted by `neurolink proxy replay <export|compare>`. */
802
+ export type ProxyReplayArgs = {
803
+ action?: "export" | "compare";
804
+ requestId?: string;
805
+ attempt?: number;
806
+ logsDir?: string;
807
+ bundle?: string;
808
+ output?: string;
809
+ execute?: boolean;
810
+ headerEnv?: string[];
811
+ bodyFile?: string;
812
+ url?: string;
813
+ timeoutMs?: number;
814
+ format?: "text" | "json";
815
+ quiet?: boolean;
816
+ };
801
817
  /** Arguments accepted by hidden `neurolink proxy guard` command */
802
818
  export type ProxyGuardArgs = {
803
819
  host?: string;
@@ -1227,6 +1227,127 @@ export type ProxyBodyCaptureEntry = {
1227
1227
  spanId?: string;
1228
1228
  metadata?: Record<string, unknown>;
1229
1229
  };
1230
+ /** JSON object retained in deterministic proxy replay artifacts. */
1231
+ export type ProxyReplayJsonRecord = Record<string, unknown>;
1232
+ /** One verified body-capture record in a proxy replay bundle. */
1233
+ export type ProxyReplayCapture = {
1234
+ timestamp: string;
1235
+ phase: string;
1236
+ attempt: number | null;
1237
+ model: string | null;
1238
+ stream: boolean | null;
1239
+ account: string | null;
1240
+ accountType: string | null;
1241
+ responseStatus: number | null;
1242
+ durationMs: number | null;
1243
+ contentType: string | null;
1244
+ headers: Record<string, string>;
1245
+ body: string | null;
1246
+ bodySha256: string | null;
1247
+ bodyTruncated: boolean;
1248
+ observedBodyBytes: number | null;
1249
+ metadata: ProxyReplayJsonRecord | null;
1250
+ source: {
1251
+ indexFile: string;
1252
+ indexLine: number;
1253
+ artifactPath: string | null;
1254
+ };
1255
+ issues: string[];
1256
+ };
1257
+ /** Deterministic, redacted reconstruction of one captured proxy request. */
1258
+ export type ProxyReplayBundle = {
1259
+ schemaVersion: 1;
1260
+ kind: "neurolink.proxy.replay-bundle";
1261
+ requestId: string;
1262
+ selectedAttempt: number;
1263
+ source: {
1264
+ logsDirectory: string;
1265
+ indexFiles: string[];
1266
+ };
1267
+ completeness: {
1268
+ captures: number;
1269
+ phasesPresent: string[];
1270
+ missingRequiredPhases: string[];
1271
+ truncatedCaptures: number;
1272
+ artifactsWithIssues: number;
1273
+ replayable: boolean;
1274
+ blockers: string[];
1275
+ };
1276
+ request: {
1277
+ method: string;
1278
+ url: string | null;
1279
+ headers: Record<string, string>;
1280
+ requiredHeaderInputs: string[];
1281
+ body: string | null;
1282
+ bodySha256: string | null;
1283
+ bodyTruncated: boolean;
1284
+ contentType: string | null;
1285
+ account: string | null;
1286
+ accountType: string | null;
1287
+ model: string | null;
1288
+ stream: boolean | null;
1289
+ };
1290
+ capturedResponse: ProxyReplayCapture | null;
1291
+ captures: ProxyReplayCapture[];
1292
+ };
1293
+ /** Redacted direct-upstream response and comparison with captured evidence. */
1294
+ export type ProxyReplayComparison = {
1295
+ schemaVersion: 1;
1296
+ kind: "neurolink.proxy.replay-comparison";
1297
+ requestId: string;
1298
+ selectedAttempt: number;
1299
+ endpoint: string;
1300
+ request: {
1301
+ method: string;
1302
+ headers: Record<string, string>;
1303
+ bodySha256: string;
1304
+ bodyBytes: number;
1305
+ usedBodyOverride: boolean;
1306
+ };
1307
+ captured: {
1308
+ status: number | null;
1309
+ contentType: string | null;
1310
+ bodySha256: string | null;
1311
+ bodyBytes: number | null;
1312
+ bodyTruncated: boolean;
1313
+ jsonShape: string[] | null;
1314
+ } | null;
1315
+ direct: {
1316
+ status: number;
1317
+ headers: Record<string, string>;
1318
+ contentType: string | null;
1319
+ body: string;
1320
+ bodySha256: string;
1321
+ observedBodyBytes: number;
1322
+ storedBodyBytes: number;
1323
+ bodyTruncated: boolean;
1324
+ timeToHeadersMs: number;
1325
+ totalMs: number;
1326
+ jsonShape: string[] | null;
1327
+ };
1328
+ comparison: {
1329
+ statusMatches: boolean | null;
1330
+ contentTypeMatches: boolean | null;
1331
+ bodyHashMatches: boolean | null;
1332
+ jsonShapeMatches: boolean | null;
1333
+ };
1334
+ };
1335
+ /** Inputs for deterministic proxy replay bundle export. */
1336
+ export type ExportProxyReplayOptions = {
1337
+ requestId: string;
1338
+ logsDir?: string;
1339
+ attempt?: number;
1340
+ };
1341
+ /** Inputs for an explicitly authorized direct-upstream comparison. */
1342
+ export type CompareProxyReplayOptions = {
1343
+ execute: boolean;
1344
+ headerValues?: Record<string, string>;
1345
+ bodyOverride?: string;
1346
+ urlOverride?: string;
1347
+ timeoutMs?: number;
1348
+ now?: () => number;
1349
+ fetchImpl?: typeof fetch;
1350
+ };
1230
1351
  /** Persisted artifact produced when a body is stored to disk. */
1231
1352
  export type StoredBodyArtifact = {
1232
1353
  bodyPath?: string;
@@ -0,0 +1,10 @@
1
+ import type { CompareProxyReplayOptions, ExportProxyReplayOptions, ProxyReplayBundle, ProxyReplayComparison } from "../types/index.js";
2
+ export declare function isValidProxyReplayHeaderName(name: string): boolean;
3
+ export declare function serializeProxyReplayDocument(value: unknown): string;
4
+ export declare function exportProxyReplayBundle(options: ExportProxyReplayOptions): Promise<ProxyReplayBundle>;
5
+ export declare function writeProxyReplayDocument(outputPath: string, document: unknown): Promise<{
6
+ path: string;
7
+ sha256: string;
8
+ }>;
9
+ export declare function readProxyReplayBundle(bundlePath: string): Promise<ProxyReplayBundle>;
10
+ export declare function compareProxyReplayBundle(bundle: ProxyReplayBundle, options: CompareProxyReplayOptions): Promise<ProxyReplayComparison>;