@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.
- package/CHANGELOG.md +12 -0
- package/dist/browser/neurolink.min.js +379 -382
- package/dist/cli/commands/proxy.d.ts +5 -3
- package/dist/cli/commands/proxy.js +360 -69
- package/dist/cli/commands/proxyAnalyze.d.ts +3 -0
- package/dist/cli/commands/proxyAnalyze.js +147 -0
- package/dist/cli/parser.js +3 -1
- package/dist/lib/proxy/proxyActivity.d.ts +2 -2
- package/dist/lib/proxy/proxyActivity.js +49 -9
- package/dist/lib/proxy/proxyAnalysis.d.ts +3 -0
- package/dist/lib/proxy/proxyAnalysis.js +454 -0
- package/dist/lib/proxy/proxyHealth.d.ts +4 -0
- package/dist/lib/proxy/proxyHealth.js +21 -0
- package/dist/lib/proxy/proxyLifecycle.d.ts +8 -0
- package/dist/lib/proxy/proxyLifecycle.js +333 -0
- package/dist/lib/proxy/requestLogger.js +6 -0
- package/dist/lib/proxy/sseInterceptor.d.ts +9 -1
- package/dist/lib/proxy/sseInterceptor.js +6 -5
- package/dist/lib/proxy/streamOutcome.d.ts +3 -1
- package/dist/lib/proxy/streamOutcome.js +77 -0
- package/dist/lib/proxy/updateCoordinator.d.ts +7 -0
- package/dist/lib/proxy/updateCoordinator.js +60 -0
- package/dist/lib/proxy/updateState.d.ts +4 -0
- package/dist/lib/proxy/updateState.js +51 -0
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +1 -0
- package/dist/lib/server/routes/claudeProxyRoutes.js +137 -35
- package/dist/lib/types/cli.d.ts +7 -0
- package/dist/lib/types/proxy.d.ts +269 -0
- package/dist/proxy/proxyActivity.d.ts +2 -2
- package/dist/proxy/proxyActivity.js +49 -9
- package/dist/proxy/proxyAnalysis.d.ts +3 -0
- package/dist/proxy/proxyAnalysis.js +453 -0
- package/dist/proxy/proxyHealth.d.ts +4 -0
- package/dist/proxy/proxyHealth.js +21 -0
- package/dist/proxy/proxyLifecycle.d.ts +8 -0
- package/dist/proxy/proxyLifecycle.js +332 -0
- package/dist/proxy/requestLogger.js +6 -0
- package/dist/proxy/sseInterceptor.d.ts +9 -1
- package/dist/proxy/sseInterceptor.js +6 -5
- package/dist/proxy/streamOutcome.d.ts +3 -1
- package/dist/proxy/streamOutcome.js +77 -0
- package/dist/proxy/updateCoordinator.d.ts +7 -0
- package/dist/proxy/updateCoordinator.js +59 -0
- package/dist/proxy/updateState.d.ts +4 -0
- package/dist/proxy/updateState.js +51 -0
- package/dist/server/routes/claudeProxyRoutes.d.ts +1 -0
- package/dist/server/routes/claudeProxyRoutes.js +137 -35
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/proxy.d.ts +269 -0
- 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
|
package/dist/cli/parser.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
98
|
+
settle("stream_error");
|
|
55
99
|
controller.error(error);
|
|
56
100
|
}
|
|
57
101
|
},
|
|
58
102
|
async cancel(reason) {
|
|
59
|
-
|
|
60
|
-
|
|
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, {
|