@juspay/neurolink 12.14.7 → 12.14.9

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 (49) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/dist/adapters/providerImageAdapter.js +1 -0
  3. package/dist/browser/neurolink.min.js +379 -379
  4. package/dist/cli/commands/proxy.js +7 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +11 -0
  7. package/dist/mcp/servers/aiProviders/aiAnalysisTools.js +43 -30
  8. package/dist/neurolink.d.ts +4 -1
  9. package/dist/neurolink.js +41 -6
  10. package/dist/providers/azureOpenai.js +1 -1
  11. package/dist/providers/catalog/loader.d.ts +1 -1
  12. package/dist/providers/catalog/loader.js +30 -1
  13. package/dist/providers/cohere.js +1 -1
  14. package/dist/providers/deepseek.js +1 -1
  15. package/dist/providers/googleAiStudio/client.js +6 -5
  16. package/dist/providers/googleVertex/client.d.ts +11 -30
  17. package/dist/providers/googleVertex/client.js +49 -131
  18. package/dist/providers/huggingFace/client.js +1 -1
  19. package/dist/providers/llamaCpp.js +1 -1
  20. package/dist/providers/lmStudio.js +1 -1
  21. package/dist/providers/nvidiaNim/client.js +1 -1
  22. package/dist/providers/ollama/client.js +1 -1
  23. package/dist/providers/openRouter/client.js +1 -1
  24. package/dist/providers/openaiCompatible/client.js +1 -1
  25. package/dist/proxy/bodyCaptureProcessing.js +47 -3
  26. package/dist/proxy/bodyCaptureWorker.js +14 -10
  27. package/dist/proxy/claudeOutputObservation.d.ts +9 -0
  28. package/dist/proxy/claudeOutputObservation.js +85 -0
  29. package/dist/proxy/codexUsage.js +12 -10
  30. package/dist/proxy/otelLogSink.js +1 -1
  31. package/dist/proxy/processTelemetry.d.ts +3 -0
  32. package/dist/proxy/processTelemetry.js +25 -0
  33. package/dist/proxy/rawStreamCapture.js +17 -26
  34. package/dist/proxy/requestLogger.d.ts +2 -0
  35. package/dist/proxy/requestLogger.js +5 -1
  36. package/dist/proxy/restartControl.d.ts +3 -1
  37. package/dist/proxy/restartControl.js +118 -1
  38. package/dist/proxy/sseInterceptor.js +71 -3
  39. package/dist/server/routes/claudeProxyRoutes.d.ts +0 -11
  40. package/dist/server/routes/claudeProxyRoutes.js +27 -12
  41. package/dist/server/routes/codexProxyRoutes.js +83 -2
  42. package/dist/types/proxy.d.ts +14 -0
  43. package/dist/types/proxyRestart.d.ts +25 -0
  44. package/dist/utils/logSanitize.js +1 -1
  45. package/dist/utils/timeout.d.ts +13 -2
  46. package/dist/utils/timeout.js +14 -3
  47. package/docs-site/static/search-index.json +1 -1
  48. package/package.json +1 -1
  49. package/scripts/observability/proxy-telemetry-check.mjs +39 -5
@@ -25,7 +25,10 @@ import { createCodexUsageTap } from "../../proxy/codexUsage.js";
25
25
  import { CODEX_ACCOUNT_PREFIX, parseCodexRateLimitHeaders, } from "../../proxy/codexAccountUsage.js";
26
26
  import { buildClientAttribution } from "../../proxy/clientAttribution.js";
27
27
  import { registerProxyResponseObserver } from "../../proxy/proxyActivity.js";
28
- import { logRequest, logRequestAttempt } from "../../proxy/requestLogger.js";
28
+ import { logRequest, logRequestAttempt, logBodyCapture, isProxyBodyCaptureEnabled, } from "../../proxy/requestLogger.js";
29
+ import { createRawStreamCapture } from "../../proxy/rawStreamCapture.js";
30
+ import { resolveProxyLogTraceContext } from "../../proxy/proxyTraceContext.js";
31
+ import { isBorrowedRequest } from "../../proxy/shareContext.js";
29
32
  import { ProxyTracer } from "../../proxy/proxyTracer.js";
30
33
  import { parseRetryAfterMs } from "../../proxy/routingPolicy.js";
31
34
  import { recordAttempt, recordAttemptError, recordFinalError, recordFinalSuccess, } from "../../proxy/usageStats.js";
@@ -272,6 +275,68 @@ function publishCodexHeaders(ctx, account, attempt, quota) {
272
275
  }
273
276
  /** Core pooled handler for POST /backend-api/codex/responses. */
274
277
  export async function handleCodexResponsesRequest(ctx) {
278
+ if (ctx.metadata?.[CODEX_FALLBACK_METADATA_KEY] !== true) {
279
+ void logBodyCapture({
280
+ timestamp: new Date().toISOString(),
281
+ requestId: ctx.requestId,
282
+ phase: "client_request",
283
+ model: codexCaptureModel(ctx),
284
+ stream: true,
285
+ headers: ctx.headers,
286
+ body: ctx.body,
287
+ contentType: "application/json",
288
+ ...resolveProxyLogTraceContext({ requestId: ctx.requestId }),
289
+ });
290
+ }
291
+ const response = await executeCodexResponsesRequest(ctx);
292
+ return ctx.metadata?.[CODEX_FALLBACK_METADATA_KEY] === true
293
+ ? response
294
+ : captureCodexResponse(ctx, response, "client_response");
295
+ }
296
+ function codexCaptureModel(ctx) {
297
+ const body = ctx.body;
298
+ return typeof body?.model === "string" ? body.model : "-";
299
+ }
300
+ /** Observe bytes through the existing bounded pass-through; cancellation still reaches upstream. */
301
+ function captureCodexResponse(ctx, response, phase, account, attempt) {
302
+ if (!isProxyBodyCaptureEnabled()) {
303
+ return response;
304
+ }
305
+ const metadata = {
306
+ timestamp: new Date().toISOString(),
307
+ requestId: ctx.requestId,
308
+ phase,
309
+ model: codexCaptureModel(ctx),
310
+ stream: true,
311
+ headers: Object.fromEntries(response.headers.entries()),
312
+ contentType: response.headers.get("content-type") ?? undefined,
313
+ responseStatus: response.status,
314
+ account: account?.label,
315
+ accountType: account ? CODEX_ACCOUNT_TYPE : undefined,
316
+ attempt,
317
+ ...resolveProxyLogTraceContext({ requestId: ctx.requestId }),
318
+ };
319
+ if (!response.body || isBorrowedRequest()) {
320
+ void logBodyCapture(metadata);
321
+ return response;
322
+ }
323
+ const observed = createRawStreamCapture();
324
+ void observed.capture
325
+ .then((capture) => logBodyCapture({
326
+ ...metadata,
327
+ timestamp: new Date().toISOString(),
328
+ body: capture.text,
329
+ bodySize: capture.totalBytes,
330
+ sourceTruncated: capture.truncated,
331
+ }))
332
+ .catch(() => undefined);
333
+ return new Response(response.body.pipeThrough(observed.stream), {
334
+ status: response.status,
335
+ statusText: response.statusText,
336
+ headers: response.headers,
337
+ });
338
+ }
339
+ async function executeCodexResponsesRequest(ctx) {
275
340
  const requestStartTime = Date.now();
276
341
  const body = ctx.body ?? {};
277
342
  const bodyStr = JSON.stringify(body);
@@ -475,9 +540,24 @@ export async function handleCodexResponsesRequest(ctx) {
475
540
  recordAttempt(account.label, CODEX_ACCOUNT_TYPE);
476
541
  let upstream;
477
542
  try {
543
+ const upstreamHeaders = buildCodexUpstreamHeaders(ctx.headers, account);
544
+ void logBodyCapture({
545
+ timestamp: new Date().toISOString(),
546
+ requestId: ctx.requestId,
547
+ phase: "upstream_request",
548
+ model,
549
+ stream: true,
550
+ body: bodyStr,
551
+ headers: upstreamHeaders,
552
+ contentType: "application/json",
553
+ account: account.label,
554
+ accountType: CODEX_ACCOUNT_TYPE,
555
+ attempt,
556
+ ...tracer?.getTraceContext(),
557
+ });
478
558
  upstream = await fetch(CODEX_RESPONSES_URL, {
479
559
  method: "POST",
480
- headers: buildCodexUpstreamHeaders(ctx.headers, account),
560
+ headers: upstreamHeaders,
481
561
  body: bodyStr,
482
562
  signal: ctx.abortSignal
483
563
  ? AbortSignal.any([
@@ -541,6 +621,7 @@ export async function handleCodexResponsesRequest(ctx) {
541
621
  lastErrorStatus = 502;
542
622
  break; // rotate to next account
543
623
  }
624
+ upstream = captureCodexResponse(ctx, upstream, "upstream_response", account, attempt);
544
625
  if (upstream.ok) {
545
626
  const quota = parseCodexRateLimitHeaders(upstream.headers);
546
627
  if (quota) {
@@ -561,6 +561,8 @@ export type RequestLogEntry = {
561
561
  firstUsefulOutputStatus?: "observed" | "no_useful_output" | "not_observed";
562
562
  /** Native protocol event that established the first useful output. */
563
563
  firstUsefulOutputEvent?: string;
564
+ /** Why first-output timing could not be established for this response. */
565
+ firstUsefulOutputUnavailableReason?: string;
564
566
  /** Requested reasoning effort retained independently of body capture. */
565
567
  reasoningEffort?: string;
566
568
  /** Small routing evidence retained even when response bodies are pruned. */
@@ -645,6 +647,8 @@ export type ProcessedProxyBodyCapture = {
645
647
  export type ProxyBodyCaptureAdmission = {
646
648
  limitingResource: "entry" | "captures" | "bytes" | "worker";
647
649
  estimatedBytes?: number;
650
+ /** Per-entry clone ceiling, independent of the aggregate queue ceiling. */
651
+ maxEntryBytes?: number;
648
652
  pending: number;
649
653
  pendingBytes: number;
650
654
  maxPending: number;
@@ -739,6 +743,7 @@ export type ProxyTelemetryCheck = {
739
743
  };
740
744
  /** Small stored metadata used by the doctor; bodies are queried separately. */
741
745
  export type ProxyTelemetryStoredRecord = Partial<RequestLogEntry> & {
746
+ recordedAtMicroseconds?: number;
742
747
  captureId?: string;
743
748
  bodySha256?: string;
744
749
  redactedBodyBytes?: number;
@@ -837,6 +842,8 @@ export type CodexTokenRefresher = (refreshToken: string) => Promise<{
837
842
  }>;
838
843
  export type ProxyBodyCaptureInput = {
839
844
  phase: string;
845
+ /** The stream observer retained only a bounded prefix. */
846
+ sourceTruncated?: boolean;
840
847
  headers?: Record<string, string>;
841
848
  body?: unknown;
842
849
  bodySize?: number;
@@ -2165,6 +2172,8 @@ export type ProxyBodyCaptureEntry = {
2165
2172
  headers?: Record<string, string>;
2166
2173
  body?: unknown;
2167
2174
  bodySize?: number;
2175
+ /** The bounded stream observer omitted a suffix before redaction/processing. */
2176
+ sourceTruncated?: boolean;
2168
2177
  contentType?: string;
2169
2178
  responseStatus?: number;
2170
2179
  durationMs?: number;
@@ -2335,6 +2344,8 @@ export type SSEContentBlock = {
2335
2344
  export type SSETelemetry = {
2336
2345
  messageStopReceived: boolean;
2337
2346
  firstUsefulOutputAt?: number;
2347
+ firstUsefulOutputEvent?: string;
2348
+ observationIncomplete?: boolean;
2338
2349
  messageId: string;
2339
2350
  model: string;
2340
2351
  usage: {
@@ -2358,6 +2369,7 @@ export type SSETelemetry = {
2358
2369
  /** Error carried as a terminal SSE `event: error`, if one was observed. */
2359
2370
  streamErrorMessage?: string;
2360
2371
  rawText?: string;
2372
+ rawTextTruncated?: boolean;
2361
2373
  };
2362
2374
  /** Terminal outcome of a response body after the HTTP headers were sent. */
2363
2375
  export type StreamTerminalOutcome = {
@@ -2379,6 +2391,8 @@ export type StreamTerminalOutcomeTracker = {
2379
2391
  export type TelemetryAccumulator = {
2380
2392
  messageStopReceived: boolean;
2381
2393
  firstUsefulOutputAt?: number;
2394
+ firstUsefulOutputEvent?: string;
2395
+ observationIncomplete?: boolean;
2382
2396
  messageId: string;
2383
2397
  model: string;
2384
2398
  inputTokens: number;
@@ -44,6 +44,31 @@ export type ProxyRestartControlOptions = {
44
44
  getInstalledVersion: () => Promise<string | undefined>;
45
45
  isUpdatePending: () => boolean;
46
46
  getStatus: () => Promise<unknown>;
47
+ /** Current supervisor process evidence; never infer it from a worker/plist. */
48
+ getTelemetry?: () => ProxyProcessTelemetrySnapshot;
47
49
  /** Report control-server errors without stopping the serving listener. */
48
50
  log?: (message: string) => void;
49
51
  };
52
+ /** Small read-only process evidence, excluding environment variables and log content. */
53
+ export type ProxyProcessTelemetrySnapshot = {
54
+ pid: number;
55
+ checkedAt: string;
56
+ configuredSink: "otel" | "file";
57
+ lifecycleSink: string;
58
+ otelInitialized: boolean;
59
+ stdio: {
60
+ stdout: "file" | "non_file" | "unavailable";
61
+ stderr: "file" | "non_file" | "unavailable";
62
+ };
63
+ exportDropped: number;
64
+ exportUnconfirmed: number;
65
+ };
66
+ export type ProxySupervisorTelemetry = {
67
+ status: "available";
68
+ process: ProxyProcessTelemetrySnapshot;
69
+ } | {
70
+ status: "unavailable";
71
+ reason: string;
72
+ } | {
73
+ status: "not_applicable";
74
+ };
@@ -47,7 +47,7 @@ const SECRET_PATTERN = new RegExp(
47
47
  // Bare tokens by known prefix (e.g. `sk-abc…`, `r8_xyz…`)
48
48
  `|\\b(?:${PREFIX_PATTERN})[_\\-][A-Za-z0-9_\\-\\.]{8,}\\b` +
49
49
  // Generic key=value pairs (URL params, JSON bodies, header dumps)
50
- "|\\b(?:api[_-]?key|access[_-]?token|secret[_-]?key|refresh[_-]?token)\\s*[:=]\\s*['\"]?[^\\s,;'\"&]+", "gi");
50
+ "|\\b(?:api[_-]?key|access[_-]?token|secret[_-]?key|refresh[_-]?token)['\"]?\\s*[:=]\\s*['\"]?[^\\s,;'\"&]+", "gi");
51
51
  /** Header names that should always be redacted regardless of value shape. */
52
52
  const SENSITIVE_HEADER_NAMES = [
53
53
  "authorization",
@@ -5,10 +5,21 @@
5
5
  * Supports multiple time formats: milliseconds, seconds, minutes, hours.
6
6
  */
7
7
  import type { TimeoutConfig, TimeoutResult } from "../types/index.js";
8
+ import { TimeoutError as BaseTimeoutError } from "./async/withTimeout.js";
8
9
  /**
9
- * Custom error class for timeout operations
10
+ * Custom error class for timeout operations.
11
+ *
12
+ * Extends the base {@link BaseTimeoutError} (from `./async/withTimeout.js`)
13
+ * rather than `Error` directly, merging what were two independent internal
14
+ * `TimeoutError` classes onto one prototype chain — an `instanceof` check
15
+ * against either module's `TimeoutError` now recognizes errors thrown by
16
+ * this class. Constructor signature, `.name`, and the extra
17
+ * `timeout`/`provider`/`operation` fields are unchanged so all 15 existing
18
+ * `instanceof TimeoutError` call sites keep working exactly as before;
19
+ * `super(message, timeout)` additionally populates the base class's
20
+ * `timeoutMs` field (equal to `timeout`) as a byproduct.
10
21
  */
11
- export declare class TimeoutError extends Error {
22
+ export declare class TimeoutError extends BaseTimeoutError {
12
23
  readonly timeout: number;
13
24
  readonly provider?: string | undefined;
14
25
  readonly operation?: "generate" | "stream" | undefined;
@@ -4,15 +4,26 @@
4
4
  * Provides flexible timeout parsing and error handling for AI operations.
5
5
  * Supports multiple time formats: milliseconds, seconds, minutes, hours.
6
6
  */
7
+ import { TimeoutError as BaseTimeoutError } from "./async/withTimeout.js";
7
8
  /**
8
- * Custom error class for timeout operations
9
+ * Custom error class for timeout operations.
10
+ *
11
+ * Extends the base {@link BaseTimeoutError} (from `./async/withTimeout.js`)
12
+ * rather than `Error` directly, merging what were two independent internal
13
+ * `TimeoutError` classes onto one prototype chain — an `instanceof` check
14
+ * against either module's `TimeoutError` now recognizes errors thrown by
15
+ * this class. Constructor signature, `.name`, and the extra
16
+ * `timeout`/`provider`/`operation` fields are unchanged so all 15 existing
17
+ * `instanceof TimeoutError` call sites keep working exactly as before;
18
+ * `super(message, timeout)` additionally populates the base class's
19
+ * `timeoutMs` field (equal to `timeout`) as a byproduct.
9
20
  */
10
- export class TimeoutError extends Error {
21
+ export class TimeoutError extends BaseTimeoutError {
11
22
  timeout;
12
23
  provider;
13
24
  operation;
14
25
  constructor(message, timeout, provider, operation) {
15
- super(message);
26
+ super(message, timeout);
16
27
  this.timeout = timeout;
17
28
  this.provider = provider;
18
29
  this.operation = operation;