@juspay/neurolink 12.12.8 → 12.12.10

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +2 -3
  2. package/dist/browser/neurolink.min.js +394 -395
  3. package/dist/cli/commands/proxy.d.ts +4 -0
  4. package/dist/cli/commands/proxy.js +60 -18
  5. package/dist/cli/commands/proxyAnalyze.js +8 -1
  6. package/dist/proxy/bodyCaptureProcessing.d.ts +22 -0
  7. package/dist/proxy/bodyCaptureProcessing.js +219 -0
  8. package/dist/proxy/bodyCaptureWorker.d.ts +16 -0
  9. package/dist/proxy/bodyCaptureWorker.js +232 -0
  10. package/dist/proxy/bodyCaptureWorkerEntry.d.ts +1 -0
  11. package/dist/proxy/bodyCaptureWorkerEntry.js +34 -0
  12. package/dist/proxy/proxyAnalysis.js +159 -17
  13. package/dist/proxy/proxyLifecycle.d.ts +25 -0
  14. package/dist/proxy/proxyLifecycle.js +111 -5
  15. package/dist/proxy/proxyRequestKind.d.ts +2 -0
  16. package/dist/proxy/proxyRequestKind.js +6 -0
  17. package/dist/proxy/proxyRuntimeMetrics.d.ts +3 -0
  18. package/dist/proxy/proxyRuntimeMetrics.js +34 -0
  19. package/dist/proxy/requestLogger.d.ts +10 -6
  20. package/dist/proxy/requestLogger.js +99 -231
  21. package/dist/proxy/rollingProxyServer.js +15 -4
  22. package/dist/proxy/rollingWorkerProcess.d.ts +4 -0
  23. package/dist/proxy/rollingWorkerProcess.js +25 -8
  24. package/dist/proxy/rollingWorkerProtocol.d.ts +6 -0
  25. package/dist/proxy/rollingWorkerProtocol.js +12 -1
  26. package/dist/proxy/rollingWorkerSupervisor.d.ts +28 -0
  27. package/dist/proxy/rollingWorkerSupervisor.js +73 -25
  28. package/dist/proxy/socketWorkerRuntime.d.ts +5 -0
  29. package/dist/proxy/socketWorkerRuntime.js +19 -2
  30. package/dist/server/routes/codexProxyRoutes.js +39 -3
  31. package/dist/services/server/ai/observability/instrumentation.js +7 -1
  32. package/dist/types/cli.d.ts +1 -1
  33. package/dist/types/proxy.d.ts +69 -4
  34. package/dist/utils/schemaConversion.d.ts +9 -0
  35. package/dist/utils/schemaConversion.js +12 -1
  36. package/package.json +2 -1
@@ -5,6 +5,7 @@ import { join } from "node:path";
5
5
  import { performance } from "node:perf_hooks";
6
6
  import { withTimeout } from "../utils/async/withTimeout.js";
7
7
  import { logger } from "../utils/logger.js";
8
+ import { startProxyRuntimeMetrics } from "./proxyRuntimeMetrics.js";
8
9
  const SCHEMA_VERSION = 1;
9
10
  const DEFAULT_QUEUE_CAPACITY = 10_000;
10
11
  const DEFAULT_BATCH_SIZE = 256;
@@ -14,8 +15,12 @@ const MAX_WRITE_RETRY_DELAY_MS = 1_000;
14
15
  const LIFECYCLE_APPEND_TIMEOUT_MS = 2_000;
15
16
  const MAX_SHORT_FIELD_LENGTH = 256;
16
17
  const SESSION_KEY_FILE = ".proxy-lifecycle-session-key";
18
+ let chmodLifecycleDirectory = chmodSync;
17
19
  let loggerEnabled = false;
20
+ let loggerRequired = false;
21
+ let stopRuntimeMetrics;
18
22
  let lifecycleLogDir;
23
+ let filePrefix = "proxy-lifecycle";
19
24
  let queueCapacity = DEFAULT_QUEUE_CAPACITY;
20
25
  let batchSize = DEFAULT_BATCH_SIZE;
21
26
  let flushIntervalMs = DEFAULT_FLUSH_INTERVAL_MS;
@@ -141,6 +146,10 @@ function scheduleFlush(delayMs = flushIntervalMs) {
141
146
  }, delayMs);
142
147
  flushTimer.unref?.();
143
148
  }
149
+ /**
150
+ * Append a bounded metadata batch and confirm each admission without
151
+ * replaying ambiguous writes.
152
+ */
144
153
  async function flushBatch() {
145
154
  if (queue.length === 0) {
146
155
  return;
@@ -150,7 +159,7 @@ async function flushBatch() {
150
159
  try {
151
160
  const byPath = new Map();
152
161
  for (const item of batch) {
153
- const path = join(item.logDir, `proxy-lifecycle-${item.date}.jsonl`);
162
+ const path = join(item.logDir, `${item.filePrefix ?? "proxy-lifecycle"}-${item.date}.jsonl`);
154
163
  const items = byPath.get(path) ?? [];
155
164
  items.push(item);
156
165
  byPath.set(path, items);
@@ -167,11 +176,13 @@ async function flushBatch() {
167
176
  }, LIFECYCLE_APPEND_TIMEOUT_MS);
168
177
  timeout.unref?.();
169
178
  try {
170
- // This best-effort telemetry sink intentionally avoids fsync so request
171
- // throughput is not coupled to storage latency. Loss is surfaced by
172
- // writeDrops/writeFailures rather than delaying proxy responses.
179
+ // OS-acknowledged append survives serving-process death. This is not
180
+ // an fsync/power-loss guarantee. Admission waits on its own record.
173
181
  await appendLifecycleFile(path, lines.join(""), { mode: 0o600 });
174
182
  written += lines.length;
183
+ for (const item of items) {
184
+ item.onPersisted?.(true);
185
+ }
175
186
  }
176
187
  catch (error) {
177
188
  writeFailures += 1;
@@ -189,6 +200,9 @@ async function flushBatch() {
189
200
  ]).has(code ?? "");
190
201
  if (!definitelyNotWritten) {
191
202
  unconfirmedWrites += items.length;
203
+ for (const item of items) {
204
+ item.onPersisted?.(false);
205
+ }
192
206
  logger.warn("[proxy] lifecycle metadata append outcome is uncertain", {
193
207
  path,
194
208
  records: items.length,
@@ -211,6 +225,11 @@ async function flushBatch() {
211
225
  if (exhausted > 0) {
212
226
  dropped += exhausted;
213
227
  writeDrops += exhausted;
228
+ for (const item of items) {
229
+ if (item.writeRetries >= maxWriteRetries) {
230
+ item.onPersisted?.(false);
231
+ }
232
+ }
214
233
  }
215
234
  logger.warn("[proxy] lifecycle metadata write failed", {
216
235
  path,
@@ -234,6 +253,10 @@ async function flushBatch() {
234
253
  inFlight = Math.max(0, inFlight - batch.length);
235
254
  }
236
255
  }
256
+ /**
257
+ * Serialize batch ownership so timeouts cannot create overlapping append
258
+ * retries.
259
+ */
237
260
  function startFlush() {
238
261
  if (flushInFlight) {
239
262
  return flushInFlight;
@@ -257,10 +280,18 @@ function startFlush() {
257
280
  });
258
281
  return currentFlush;
259
282
  }
283
+ /**
284
+ * Configure private journal storage while retaining required admission
285
+ * when initialization fails.
286
+ */
260
287
  export function configureProxyLifecycleLogger(options) {
288
+ stopRuntimeMetrics?.();
289
+ stopRuntimeMetrics = undefined;
261
290
  clearScheduledFlush();
262
291
  nextFlushDelayMs = undefined;
263
292
  loggerEnabled = false;
293
+ loggerRequired = options.enabled;
294
+ filePrefix = options.filePrefix ?? "proxy-lifecycle";
264
295
  lifecycleLogDir = undefined;
265
296
  queueCapacity = positiveInteger(options.queueCapacity, DEFAULT_QUEUE_CAPACITY);
266
297
  batchSize = positiveInteger(options.batchSize, DEFAULT_BATCH_SIZE);
@@ -269,9 +300,21 @@ export function configureProxyLifecycleLogger(options) {
269
300
  if (options.enabled && options.logDir) {
270
301
  try {
271
302
  mkdirSync(options.logDir, { recursive: true, mode: 0o700 });
303
+ // mkdir mode does not harden an existing directory. Keep admission
304
+ // required but the sink disabled if its privacy boundary cannot be set.
305
+ chmodLifecycleDirectory(options.logDir, 0o700);
272
306
  sessionHashKey = resolveSessionHashKey(options.logDir);
273
307
  lifecycleLogDir = options.logDir;
274
308
  loggerEnabled = true;
309
+ stopRuntimeMetrics = startProxyRuntimeMetrics((runtimeSample) => {
310
+ logProxyLifecycleEvent({
311
+ event: "runtime_sample",
312
+ requestId: "-",
313
+ method: "-",
314
+ path: "-",
315
+ runtimeSample,
316
+ });
317
+ });
275
318
  }
276
319
  catch (error) {
277
320
  logger.warn("[proxy] lifecycle metadata logging disabled", {
@@ -286,15 +329,24 @@ export function configureProxyLifecycleLogger(options) {
286
329
  }
287
330
  /** Enqueue fixed-size lifecycle metadata without awaiting filesystem work. */
288
331
  export function logProxyLifecycleEvent(input) {
332
+ enqueueLifecycleEvent(input);
333
+ }
334
+ /**
335
+ * Enqueue bounded metadata and resolve admission failure immediately when
336
+ * capacity is unavailable.
337
+ */
338
+ function enqueueLifecycleEvent(input, onPersisted) {
289
339
  if (!loggerEnabled || !lifecycleLogDir) {
340
+ onPersisted?.(false);
290
341
  return;
291
342
  }
292
343
  try {
293
344
  attempted += 1;
294
345
  const sequence = nextSequence++;
295
- if (queue.length >= queueCapacity) {
346
+ if (queue.length + inFlight >= queueCapacity) {
296
347
  dropped += 1;
297
348
  queueDrops += 1;
349
+ onPersisted?.(false);
298
350
  return;
299
351
  }
300
352
  const timestamp = formatTimestamp(input.timestampMs);
@@ -344,12 +396,23 @@ export function logProxyLifecycleEvent(input) {
344
396
  ...(terminalOutcome !== undefined ? { terminalOutcome } : {}),
345
397
  ...(errorType !== undefined ? { errorType } : {}),
346
398
  ...(errorCode !== undefined ? { errorCode } : {}),
399
+ ...(input.supervisorEvent
400
+ ? {
401
+ supervisorEvent: {
402
+ ...input.supervisorEvent,
403
+ reason: clip(input.supervisorEvent.reason),
404
+ },
405
+ }
406
+ : {}),
407
+ ...(input.runtimeSample ? { runtimeSample: input.runtimeSample } : {}),
347
408
  };
348
409
  queue.push({
410
+ filePrefix,
349
411
  logDir: lifecycleLogDir,
350
412
  date: String(record.timestamp).slice(0, 10),
351
413
  record,
352
414
  writeRetries: 0,
415
+ onPersisted,
353
416
  });
354
417
  enqueued += 1;
355
418
  scheduleFlush();
@@ -357,6 +420,28 @@ export function logProxyLifecycleEvent(input) {
357
420
  catch {
358
421
  dropped += 1;
359
422
  invalidDrops += 1;
423
+ onPersisted?.(false);
424
+ }
425
+ }
426
+ /** Confirm admission before upstream dispatch, without waiting for later traffic.
427
+ * Disabled logging is explicit; an enabled but unhealthy sink refuses dispatch.
428
+ * A timeout never retries an ambiguous provider request or the pending append.
429
+ */
430
+ export async function persistProxyLifecycleAcceptance(input, timeoutMs = LIFECYCLE_APPEND_TIMEOUT_MS) {
431
+ if (!loggerRequired) {
432
+ return;
433
+ }
434
+ const confirmed = new Promise((resolve) => {
435
+ enqueueLifecycleEvent({ ...input, event: "request_accepted" }, resolve);
436
+ });
437
+ // Yield one turn for concurrent admissions to share a bounded batch.
438
+ clearScheduledFlush();
439
+ scheduleFlush(0);
440
+ const persisted = await withTimeout(confirmed, timeoutMs, "Proxy admission metadata append is still pending").catch(() => false);
441
+ if (!persisted) {
442
+ throw Object.assign(new Error("Proxy admission metadata could not be confirmed"), {
443
+ code: "PROXY_TELEMETRY_UNAVAILABLE",
444
+ });
360
445
  }
361
446
  }
362
447
  export async function flushProxyLifecycleEvents(timeoutMs = 5_000) {
@@ -370,6 +455,10 @@ export async function flushProxyLifecycleEvents(timeoutMs = 5_000) {
370
455
  }
371
456
  }
372
457
  }
458
+ /**
459
+ * Expose journal accounting, process identity, and outstanding writes
460
+ * without changing them.
461
+ */
373
462
  export function getProxyLifecycleLoggerSnapshot() {
374
463
  return {
375
464
  enabled: loggerEnabled,
@@ -392,9 +481,16 @@ export function getProxyLifecycleLoggerSnapshot() {
392
481
  flushing: flushInFlight !== undefined,
393
482
  };
394
483
  }
484
+ /**
485
+ * Reset timers, accounting, and injected I/O after isolated tests have
486
+ * drained their work.
487
+ */
395
488
  export function resetProxyLifecycleLoggerForTests() {
489
+ stopRuntimeMetrics?.();
490
+ stopRuntimeMetrics = undefined;
396
491
  clearScheduledFlush();
397
492
  loggerEnabled = false;
493
+ loggerRequired = false;
398
494
  lifecycleLogDir = undefined;
399
495
  queueCapacity = DEFAULT_QUEUE_CAPACITY;
400
496
  batchSize = DEFAULT_BATCH_SIZE;
@@ -419,9 +515,19 @@ export function resetProxyLifecycleLoggerForTests() {
419
515
  flushInFlight = undefined;
420
516
  nextFlushDelayMs = undefined;
421
517
  appendLifecycleFile = appendFile;
518
+ chmodLifecycleDirectory = chmodSync;
422
519
  }
423
520
  /** Isolated failure injection for lifecycle durability tests. */
424
521
  export const __proxyLifecycleTestHooks = {
522
+ /**
523
+ * Inject directory-hardening failures without altering real filesystem permissions.
524
+ */
525
+ setChmodForTests(chmod) {
526
+ chmodLifecycleDirectory = chmod;
527
+ },
528
+ /**
529
+ * Inject controlled append outcomes while preserving the production admission and queue paths.
530
+ */
425
531
  setAppendFileForTests(append) {
426
532
  appendLifecycleFile = append;
427
533
  },
@@ -0,0 +1,2 @@
1
+ /** These metadata endpoints finish as HTTP responses, without model generation. */
2
+ export declare function isProxyAuxiliaryRequest(method: string, path: string): boolean;
@@ -0,0 +1,6 @@
1
+ /** These metadata endpoints finish as HTTP responses, without model generation. */
2
+ export function isProxyAuxiliaryRequest(method, path) {
3
+ return ((method === "POST" && path === "/v1/messages/count_tokens") ||
4
+ (method === "GET" &&
5
+ (path === "/backend-api/codex/models" || path === "/v1/models")));
6
+ }
@@ -0,0 +1,3 @@
1
+ import type { ProxyRuntimeSample } from "../types/index.js";
2
+ /** Samples use actual elapsed time, including delayed timers under host load. */
3
+ export declare function startProxyRuntimeMetrics(emit: (sample: ProxyRuntimeSample) => void): () => void;
@@ -0,0 +1,34 @@
1
+ import { monitorEventLoopDelay, performance } from "node:perf_hooks";
2
+ import { availableParallelism, loadavg } from "node:os";
3
+ /** Samples use actual elapsed time, including delayed timers under host load. */
4
+ export function startProxyRuntimeMetrics(emit) {
5
+ const histogram = monitorEventLoopDelay({ resolution: 20 });
6
+ histogram.enable();
7
+ let previousCpu = process.cpuUsage();
8
+ let previousTime = performance.now();
9
+ const timer = setInterval(() => {
10
+ const now = performance.now();
11
+ const cpu = process.cpuUsage();
12
+ const intervalMs = now - previousTime;
13
+ emit({
14
+ intervalMs,
15
+ cpuPercentOneCore: ((cpu.user - previousCpu.user + cpu.system - previousCpu.system) /
16
+ (intervalMs * 1000)) *
17
+ 100,
18
+ rssBytes: process.memoryUsage().rss,
19
+ heapUsedBytes: process.memoryUsage().heapUsed,
20
+ eventLoopDelayP99Ms: histogram.count ? histogram.percentile(99) / 1e6 : 0,
21
+ eventLoopDelayMaxMs: histogram.count ? histogram.max / 1e6 : 0,
22
+ hostLoad1m: loadavg()[0],
23
+ availableParallelism: availableParallelism(),
24
+ });
25
+ previousCpu = cpu;
26
+ previousTime = now;
27
+ histogram.reset();
28
+ }, 10_000);
29
+ timer.unref();
30
+ return () => {
31
+ clearInterval(timer);
32
+ histogram.disable();
33
+ };
34
+ }
@@ -6,9 +6,15 @@
6
6
  * Useful for debugging and auditing proxy traffic.
7
7
  */
8
8
  import type { ProxyBodyCaptureEntry, RequestAttemptLogEntry, RequestLogEntry, ProxyRequestLoggerSnapshot } from "../types/index.js";
9
+ /**
10
+ * Expose independent metadata-sink and body-capture counters for incident reconciliation.
11
+ */
9
12
  export declare function getRequestLoggerSnapshot(): ProxyRequestLoggerSnapshot;
10
13
  /** Wait, up to a bounded deadline, for admitted request/body writes to settle. */
11
14
  export declare function flushRequestLogs(timeoutMs?: number): Promise<void>;
15
+ /**
16
+ * Initialize private request logs and preserve required lifecycle admission on startup failures.
17
+ */
12
18
  export declare function initRequestLogger(enabled?: boolean, customLogsDir?: string): void;
13
19
  export declare function logRequest(entry: RequestLogEntry): Promise<void>;
14
20
  /**
@@ -18,19 +24,17 @@ export declare function logRequest(entry: RequestLogEntry): Promise<void>;
18
24
  */
19
25
  export declare function logRequestAttempt(entry: RequestAttemptLogEntry): Promise<void>;
20
26
  export declare function getLogDir(): string | null;
21
- /** Shared redaction used by offline replay exports and direct comparisons. */
22
- export declare function redactProxyHeadersForLogging(headers: Record<string, string> | undefined): Record<string, string> | undefined;
23
27
  /**
24
- * Apply the same bounded body redaction used by persisted proxy captures.
25
- * `value` and `bytes` are omitted only when the input is null or undefined.
26
- * This performs serialization immediately, so callers must keep it off proxy
27
- * hot paths unless body processing has already been explicitly requested.
28
+ * Redact sensitive header values in-place.
28
29
  */
30
+ export declare function redactProxyHeadersForLogging(headers: Record<string, string> | undefined): Record<string, string> | undefined;
31
+ /** Return a redacted body representation suitable for persisted request diagnostics. */
29
32
  export declare function prepareProxyBodyForLogging(body: unknown): {
30
33
  value?: string;
31
34
  bytes?: number;
32
35
  truncated: boolean;
33
36
  };
37
+ /** Capture an owned request body with bounded processing and tracked index/export publication. */
34
38
  export declare function logBodyCapture(entry: ProxyBodyCaptureEntry): Promise<void>;
35
39
  /**
36
40
  * Log the FULL raw request and response for debugging.