@juspay/neurolink 9.92.3 → 9.93.1

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 (50) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/browser/neurolink.min.js +379 -382
  3. package/dist/cli/commands/proxy.d.ts +5 -3
  4. package/dist/cli/commands/proxy.js +360 -69
  5. package/dist/cli/commands/proxyAnalyze.d.ts +3 -0
  6. package/dist/cli/commands/proxyAnalyze.js +147 -0
  7. package/dist/cli/parser.js +3 -1
  8. package/dist/lib/proxy/proxyActivity.d.ts +2 -2
  9. package/dist/lib/proxy/proxyActivity.js +49 -9
  10. package/dist/lib/proxy/proxyAnalysis.d.ts +3 -0
  11. package/dist/lib/proxy/proxyAnalysis.js +454 -0
  12. package/dist/lib/proxy/proxyHealth.d.ts +4 -0
  13. package/dist/lib/proxy/proxyHealth.js +21 -0
  14. package/dist/lib/proxy/proxyLifecycle.d.ts +8 -0
  15. package/dist/lib/proxy/proxyLifecycle.js +333 -0
  16. package/dist/lib/proxy/requestLogger.js +6 -0
  17. package/dist/lib/proxy/sseInterceptor.d.ts +9 -1
  18. package/dist/lib/proxy/sseInterceptor.js +6 -5
  19. package/dist/lib/proxy/streamOutcome.d.ts +3 -1
  20. package/dist/lib/proxy/streamOutcome.js +77 -0
  21. package/dist/lib/proxy/updateCoordinator.d.ts +7 -0
  22. package/dist/lib/proxy/updateCoordinator.js +60 -0
  23. package/dist/lib/proxy/updateState.d.ts +4 -0
  24. package/dist/lib/proxy/updateState.js +51 -0
  25. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +1 -0
  26. package/dist/lib/server/routes/claudeProxyRoutes.js +137 -35
  27. package/dist/lib/types/cli.d.ts +7 -0
  28. package/dist/lib/types/proxy.d.ts +269 -0
  29. package/dist/proxy/proxyActivity.d.ts +2 -2
  30. package/dist/proxy/proxyActivity.js +49 -9
  31. package/dist/proxy/proxyAnalysis.d.ts +3 -0
  32. package/dist/proxy/proxyAnalysis.js +453 -0
  33. package/dist/proxy/proxyHealth.d.ts +4 -0
  34. package/dist/proxy/proxyHealth.js +21 -0
  35. package/dist/proxy/proxyLifecycle.d.ts +8 -0
  36. package/dist/proxy/proxyLifecycle.js +332 -0
  37. package/dist/proxy/requestLogger.js +6 -0
  38. package/dist/proxy/sseInterceptor.d.ts +9 -1
  39. package/dist/proxy/sseInterceptor.js +6 -5
  40. package/dist/proxy/streamOutcome.d.ts +3 -1
  41. package/dist/proxy/streamOutcome.js +77 -0
  42. package/dist/proxy/updateCoordinator.d.ts +7 -0
  43. package/dist/proxy/updateCoordinator.js +59 -0
  44. package/dist/proxy/updateState.d.ts +4 -0
  45. package/dist/proxy/updateState.js +51 -0
  46. package/dist/server/routes/claudeProxyRoutes.d.ts +1 -0
  47. package/dist/server/routes/claudeProxyRoutes.js +137 -35
  48. package/dist/types/cli.d.ts +7 -0
  49. package/dist/types/proxy.d.ts +269 -0
  50. package/package.json +4 -1
@@ -0,0 +1,147 @@
1
+ import chalk from "chalk";
2
+ import { analyzeProxyLogs } from "../../lib/proxy/proxyAnalysis.js";
3
+ import { logger } from "../../lib/utils/logger.js";
4
+ function formatLatency(label, summary) {
5
+ const value = (amount) => amount === null ? "-" : amount.toFixed(1);
6
+ return `${label.padEnd(22)} ${String(summary.count).padStart(7)} ${value(summary.p50).padStart(9)} ${value(summary.p95).padStart(9)} ${value(summary.p99).padStart(9)} ${value(summary.max).padStart(9)}`;
7
+ }
8
+ function printAnalysis(report) {
9
+ logger.always("");
10
+ logger.always(chalk.bold.cyan("NeuroLink Proxy Analysis"));
11
+ logger.always(chalk.gray("=".repeat(50)));
12
+ logger.always(` Since: ${chalk.cyan(report.since)}`);
13
+ logger.always(` Logs: ${chalk.cyan(report.logsDir)}`);
14
+ logger.always(` Files: ${report.files.requests} request, ${report.files.attempts} attempt, ${report.files.lifecycle} lifecycle`);
15
+ logger.always("");
16
+ logger.always(chalk.bold(" Reliability"));
17
+ logger.always(report.coverage.finalRequests
18
+ ? ` Completed: ${report.requests.completed} (${chalk.green(`${report.requests.success} success`)}, ${chalk.red(`${report.requests.errors} errors`)})`
19
+ : chalk.yellow(" Completed: unavailable (no final request logs)"));
20
+ logger.always(report.coverage.attempts && report.coverage.finalRequests
21
+ ? ` Recovered after retry: ${report.requests.recoveredAfterRetry}`
22
+ : chalk.yellow(" Recovered after retry: unavailable"));
23
+ if (report.requests.errors > 0) {
24
+ logger.always(` Final error types: ${JSON.stringify(report.requests.errorTypes)}`);
25
+ }
26
+ if (report.coverage.attempts) {
27
+ logger.always(` Attempts: ${report.attempts.total}, ${report.attempts.errors} errors${report.attempts.errors > 0 ? ` ${JSON.stringify(report.attempts.errorTypes)}` : ""}`);
28
+ }
29
+ logger.always(report.coverage.lifecycle
30
+ ? ` Lifecycle: ${report.lifecycle.accepted} accepted, ${report.lifecycle.terminal} terminal, ${report.lifecycle.unsettled} unsettled`
31
+ : chalk.yellow(" Lifecycle: unavailable (no lifecycle metadata)"));
32
+ if (report.coverage.attempts) {
33
+ const finalRateLimits = report.coverage.finalRequests
34
+ ? `${report.requests.finalRateLimits} final`
35
+ : "final unavailable";
36
+ logger.always(` Rate limits: ${report.rateLimits.attemptRateLimits} attempts (${report.rateLimits.quota} quota, ${report.rateLimits.transient} transient, ${report.rateLimits.unclassified} unclassified), ${finalRateLimits}`);
37
+ }
38
+ else if (report.coverage.finalRequests) {
39
+ logger.always(chalk.yellow(` Rate limits: attempt classification unavailable; ${report.requests.finalRateLimits} final 429 response(s)`));
40
+ }
41
+ else {
42
+ logger.always(chalk.yellow(" Rate limits: unavailable"));
43
+ }
44
+ logger.always("");
45
+ logger.always(chalk.bold(" Latency (ms)"));
46
+ const latencyHeader = `${"METRIC".padEnd(22)} ${"COUNT".padStart(7)} ${"P50".padStart(9)} ${"P95".padStart(9)} ${"P99".padStart(9)} ${"MAX".padStart(9)}`;
47
+ logger.always(` ${chalk.gray(latencyHeader)}`);
48
+ logger.always(` ${chalk.gray("-".repeat(latencyHeader.length))}`);
49
+ for (const [label, summary] of [
50
+ ["Response headers", report.latencyMs.headers],
51
+ ["First chunk", report.latencyMs.firstChunk],
52
+ ["Terminal", report.latencyMs.terminal],
53
+ ["Final request log", report.latencyMs.finalRequest],
54
+ ["Account attempt", report.latencyMs.attempt],
55
+ ["Single-attempt delta", report.latencyMs.singleAttemptDelta],
56
+ ]) {
57
+ logger.always(` ${formatLatency(label, summary)}`);
58
+ }
59
+ logger.always("");
60
+ logger.always(chalk.bold(" Cache"));
61
+ if (report.coverage.cacheUsage) {
62
+ logger.always(` Usage records: ${report.cache.requestsWithUsage}, cache-read requests: ${report.cache.requestsWithCacheRead}, hit rate: ${report.cache.requestHitRate === null ? "-" : `${(report.cache.requestHitRate * 100).toFixed(1)}%`}`);
63
+ logger.always(` Tokens: ${report.cache.cacheReadTokens} read, ${report.cache.cacheCreationTokens} created, ${report.cache.inputTokens} input`);
64
+ }
65
+ else {
66
+ logger.always(chalk.yellow(" Cache usage: unavailable"));
67
+ }
68
+ logger.always("");
69
+ logger.always(chalk.bold(" Data Quality"));
70
+ logger.always(` ${report.dataQuality.linesRead} lines scanned, ${report.dataQuality.malformedLines} malformed, ${report.dataQuality.unsupportedLifecycleLines} unsupported lifecycle, ${report.dataQuality.lifecycleSequenceGaps} sequence gaps, ${report.dataQuality.lifecycleSequenceDuplicates} duplicates`);
71
+ if (report.accounts.length > 0) {
72
+ logger.always("");
73
+ logger.always(chalk.bold(" Accounts"));
74
+ const header = `${"ACCOUNT".padEnd(30)} ${"AUTH".padEnd(8)} ${"ATTEMPT".padStart(8)} ${"ATT ERR".padStart(7)} ${"FINAL".padStart(7)} ${"FIN ERR".padStart(7)} ${"QUOTA".padStart(7)} ${"TRANS".padStart(7)} ${"UNKNOWN".padStart(8)}`;
75
+ logger.always(` ${chalk.gray(header)}`);
76
+ logger.always(` ${chalk.gray("-".repeat(header.length))}`);
77
+ for (const account of report.accounts) {
78
+ const attempt = report.coverage.attempts ? String(account.attempts) : "-";
79
+ const attemptErrors = report.coverage.attempts
80
+ ? String(account.attemptErrors)
81
+ : "-";
82
+ const quota = report.coverage.attempts
83
+ ? String(account.quotaRateLimits)
84
+ : "-";
85
+ const transient = report.coverage.attempts
86
+ ? String(account.transientRateLimits)
87
+ : "-";
88
+ const unknown = report.coverage.attempts
89
+ ? String(account.unclassifiedRateLimits)
90
+ : "-";
91
+ const finalRequests = report.coverage.finalRequests
92
+ ? String(account.finalRequests)
93
+ : "-";
94
+ const finalErrors = report.coverage.finalRequests
95
+ ? String(account.finalErrors)
96
+ : "-";
97
+ logger.always(` ${account.account.slice(0, 30).padEnd(30)} ${account.accountType.slice(0, 8).padEnd(8)} ${attempt.padStart(8)} ${attemptErrors.padStart(7)} ${finalRequests.padStart(7)} ${finalErrors.padStart(7)} ${quota.padStart(7)} ${transient.padStart(7)} ${unknown.padStart(8)}`);
98
+ }
99
+ }
100
+ logger.always("");
101
+ }
102
+ export const proxyAnalyzeCommand = {
103
+ command: "analyze",
104
+ describe: "Analyze local proxy reliability and latency logs",
105
+ builder: (yargs) => yargs
106
+ .option("logs-dir", {
107
+ type: "string",
108
+ alias: "logsDir",
109
+ description: "Proxy JSONL log directory",
110
+ })
111
+ .option("since", {
112
+ type: "string",
113
+ default: "24h",
114
+ description: "ISO timestamp or lookback such as 6h, 1d, or 1w",
115
+ })
116
+ .option("format", {
117
+ type: "string",
118
+ choices: ["text", "json"],
119
+ default: "text",
120
+ description: "Output format",
121
+ })
122
+ .option("quiet", {
123
+ type: "boolean",
124
+ alias: "q",
125
+ default: false,
126
+ description: "Suppress non-essential output",
127
+ })
128
+ .example("neurolink proxy analyze --since 24h", "Analyze the last 24 hours of proxy logs"),
129
+ handler: async (argv) => {
130
+ try {
131
+ const report = await analyzeProxyLogs({
132
+ logsDir: argv.logsDir,
133
+ since: argv.since,
134
+ });
135
+ if (argv.format === "json") {
136
+ logger.always(JSON.stringify(report, null, 2));
137
+ return;
138
+ }
139
+ printAnalysis(report);
140
+ }
141
+ catch (error) {
142
+ logger.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
143
+ process.exitCode = 1;
144
+ }
145
+ },
146
+ };
147
+ //# sourceMappingURL=proxyAnalyze.js.map
@@ -14,6 +14,7 @@ import { ragCommand } from "./commands/rag.js";
14
14
  import { ObservabilityCommandFactory } from "./commands/observability.js";
15
15
  import { TelemetryCommandFactory } from "./commands/telemetry.js";
16
16
  import { proxyStartCommand, proxyStatusCommand, proxyTelemetryCommand, proxySetupCommand, proxyGuardCommand, proxyInstallCommand, proxyUninstallCommand, } from "./commands/proxy.js";
17
+ import { proxyAnalyzeCommand } from "./commands/proxyAnalyze.js";
17
18
  import { EvaluateCommandFactory } from "./commands/evaluate.js";
18
19
  import { TaskCommandFactory } from "./commands/task.js";
19
20
  import { AutoresearchCommandFactory } from "./commands/autoresearch.js";
@@ -200,12 +201,13 @@ export function initializeCliParser() {
200
201
  builder: (yargs) => yargs
201
202
  .command(proxyStartCommand)
202
203
  .command(proxyStatusCommand)
204
+ .command(proxyAnalyzeCommand)
203
205
  .command(proxyTelemetryCommand)
204
206
  .command(proxySetupCommand)
205
207
  .command(proxyGuardCommand)
206
208
  .command(proxyInstallCommand)
207
209
  .command(proxyUninstallCommand)
208
- .demandCommand(1, "Please specify a proxy subcommand: start, status, telemetry <setup|start|stop|status|logs|import-dashboard>, setup, guard, install, or uninstall"),
210
+ .demandCommand(1, "Please specify a proxy subcommand: start, status, analyze, telemetry <setup|start|stop|status|logs|import-dashboard>, setup, guard, install, or uninstall"),
209
211
  handler: () => { },
210
212
  })
211
213
  // Evaluate Command Group - Using EvaluateCommandFactory
@@ -1,8 +1,8 @@
1
- import type { ProxyActivitySnapshot } from "../types/index.js";
1
+ import type { ProxyActivitySnapshot, ProxyResponseTrackingObserver } from "../types/index.js";
2
2
  /** Track one client-facing proxy request until its response body settles. */
3
3
  export declare function beginProxyRequest(): () => void;
4
4
  export declare function getProxyActivitySnapshot(): ProxyActivitySnapshot;
5
5
  export declare function isProxyActivityQuiet(snapshot: ProxyActivitySnapshot, quietThresholdMs: number, nowMs?: number): boolean;
6
6
  /** Keep activity open until the response body completes, errors, or is cancelled. */
7
- export declare function trackProxyResponse(response: Response, finishRequest: () => void): Response;
7
+ export declare function trackProxyResponse(response: Response, finishRequest: () => void, observer?: ProxyResponseTrackingObserver): Response;
8
8
  export declare function resetProxyActivityForTests(): void;
@@ -1,8 +1,24 @@
1
+ import { withTimeout } from "../utils/async/withTimeout.js";
2
+ import { logger } from "../utils/logger.js";
3
+ const PROXY_RESPONSE_CANCEL_TIMEOUT_MS = 1_000;
1
4
  let activeRequests = 0;
2
5
  let lastActivityAtMs = null;
3
6
  function touchActivity() {
4
7
  lastActivityAtMs = Date.now();
5
8
  }
9
+ function safelyNotifyObserver(callback) {
10
+ try {
11
+ callback?.();
12
+ }
13
+ catch (error) {
14
+ // Observability must never alter response handling.
15
+ if (logger.shouldLog("debug")) {
16
+ logger.debug("[proxy] response lifecycle observer failed", {
17
+ error: error instanceof Error ? error.message : String(error),
18
+ });
19
+ }
20
+ }
21
+ }
6
22
  /** Track one client-facing proxy request until its response body settles. */
7
23
  export function beginProxyRequest() {
8
24
  activeRequests += 1;
@@ -33,35 +49,59 @@ export function isProxyActivityQuiet(snapshot, quietThresholdMs, nowMs = Date.no
33
49
  return nowMs - snapshot.lastActivityAt.getTime() >= quietThresholdMs;
34
50
  }
35
51
  /** Keep activity open until the response body completes, errors, or is cancelled. */
36
- export function trackProxyResponse(response, finishRequest) {
52
+ export function trackProxyResponse(response, finishRequest, observer) {
37
53
  if (!response.body) {
38
54
  finishRequest();
55
+ safelyNotifyObserver(() => observer?.onTerminal?.({
56
+ outcome: "bodyless",
57
+ observedBodyBytes: 0,
58
+ responseChunks: 0,
59
+ }));
39
60
  return response;
40
61
  }
41
62
  const reader = response.body.getReader();
63
+ let observedBodyBytes = 0;
64
+ let responseChunks = 0;
65
+ let settled = false;
66
+ const settle = (outcome) => {
67
+ if (settled) {
68
+ return;
69
+ }
70
+ settled = true;
71
+ finishRequest();
72
+ safelyNotifyObserver(() => observer?.onTerminal?.({
73
+ outcome,
74
+ observedBodyBytes,
75
+ responseChunks,
76
+ }));
77
+ };
42
78
  const trackedBody = new ReadableStream({
43
79
  async pull(controller) {
44
80
  try {
45
81
  const { value, done } = await reader.read();
46
82
  if (done) {
47
- finishRequest();
83
+ settle("completed");
48
84
  controller.close();
49
85
  return;
50
86
  }
51
87
  controller.enqueue(value);
88
+ observedBodyBytes += value.byteLength;
89
+ responseChunks += 1;
90
+ if (responseChunks === 1) {
91
+ safelyNotifyObserver(() => observer?.onFirstChunk?.({
92
+ observedBodyBytes,
93
+ responseChunks: 1,
94
+ }));
95
+ }
52
96
  }
53
97
  catch (error) {
54
- finishRequest();
98
+ settle("stream_error");
55
99
  controller.error(error);
56
100
  }
57
101
  },
58
102
  async cancel(reason) {
59
- try {
60
- await reader.cancel(reason);
61
- }
62
- finally {
63
- finishRequest();
64
- }
103
+ settle("client_cancelled");
104
+ await withTimeout(reader.cancel(reason), PROXY_RESPONSE_CANCEL_TIMEOUT_MS, "Timed out cancelling the upstream proxy response");
65
105
  },
66
106
  });
67
107
  return new Response(trackedBody, {
@@ -0,0 +1,3 @@
1
+ import type { ProxyAnalysisOptions, ProxyAnalysisReport } from "../types/index.js";
2
+ /** Analyze local proxy logs without reading request or response body artifacts. */
3
+ export declare function analyzeProxyLogs(options?: ProxyAnalysisOptions): Promise<ProxyAnalysisReport>;