@juspay/neurolink 12.17.0 → 12.17.2
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 +2 -7
- package/dist/browser/neurolink.min.js +422 -413
- package/dist/cli/commands/proxy.js +455 -259
- package/dist/constants/contextWindows.d.ts +2 -0
- package/dist/constants/contextWindows.js +4 -0
- package/dist/providers/anthropic/client.js +15 -5
- package/dist/proxy/anthropicBudgetResponse.d.ts +3 -0
- package/dist/proxy/anthropicBudgetResponse.js +153 -0
- package/dist/proxy/bodyCaptureProcessing.d.ts +1 -0
- package/dist/proxy/bodyCaptureProcessing.js +23 -4
- package/dist/proxy/bodyCaptureWorker.js +20 -2
- package/dist/proxy/codexFallback.d.ts +10 -0
- package/dist/proxy/codexFallback.js +108 -24
- package/dist/proxy/codexUsage.js +2 -0
- package/dist/proxy/globalInstaller.d.ts +32 -1
- package/dist/proxy/globalInstaller.js +386 -3
- package/dist/proxy/otelLogSink.d.ts +3 -3
- package/dist/proxy/otelLogSink.js +137 -144
- package/dist/proxy/otlpLogTransport.d.ts +3 -0
- package/dist/proxy/otlpLogTransport.js +334 -0
- package/dist/proxy/proxyActivity.d.ts +22 -2
- package/dist/proxy/proxyActivity.js +114 -9
- package/dist/proxy/proxyAnalysis.js +82 -52
- package/dist/proxy/proxyClientCancellation.d.ts +7 -0
- package/dist/proxy/proxyClientCancellation.js +23 -0
- package/dist/proxy/proxyContextPreflight.d.ts +25 -0
- package/dist/proxy/proxyContextPreflight.js +337 -0
- package/dist/proxy/proxyFailureDetails.d.ts +19 -0
- package/dist/proxy/proxyFailureDetails.js +96 -0
- package/dist/proxy/proxyLifecycle.js +7 -0
- package/dist/proxy/proxyRouteBodyCapture.d.ts +13 -0
- package/dist/proxy/proxyRouteBodyCapture.js +112 -0
- package/dist/proxy/proxyTokenBudget.d.ts +40 -0
- package/dist/proxy/proxyTokenBudget.js +523 -0
- package/dist/proxy/proxyTracer.d.ts +3 -2
- package/dist/proxy/proxyTracer.js +46 -20
- package/dist/proxy/proxyTranslationEngine.d.ts +8 -10
- package/dist/proxy/proxyTranslationEngine.js +736 -376
- package/dist/proxy/requestLogger.d.ts +1 -0
- package/dist/proxy/requestLogger.js +107 -5
- package/dist/proxy/restartControl.js +9 -4
- package/dist/proxy/rollingWorkerProcess.js +65 -0
- package/dist/proxy/rollingWorkerSupervisor.js +7 -0
- package/dist/proxy/updateCoordinator.d.ts +14 -1
- package/dist/proxy/updateCoordinator.js +57 -2
- package/dist/server/routes/claudeProxyRoutes.d.ts +4 -5
- package/dist/server/routes/claudeProxyRoutes.js +809 -577
- package/dist/server/routes/codexProxyRoutes.js +191 -9
- package/dist/server/routes/openaiProxyRoutes.d.ts +1 -1
- package/dist/server/routes/openaiProxyRoutes.js +261 -69
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/codex.d.ts +5 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/proxy.d.ts +148 -3
- package/dist/types/proxy.js +0 -15
- package/dist/types/proxyBudget.d.ts +74 -0
- package/dist/types/proxyBudget.js +1 -0
- package/dist/types/proxyContext.d.ts +35 -0
- package/dist/types/proxyContext.js +1 -0
- package/dist/utils/toolChoice.d.ts +19 -0
- package/dist/utils/toolChoice.js +39 -0
- package/docs/assets/dashboards/README.md +37 -0
- package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +197 -89
- package/docs-site/static/search-index.json +1 -1
- package/package.json +4 -1
- package/scripts/observability/NATIVE-DURABLE.md +63 -0
- package/scripts/observability/otel-collector.proxy-native-durable.yaml +93 -0
- package/scripts/observability/proxy-telemetry-backend.mjs +32 -5
- package/scripts/observability/proxy-telemetry-check.mjs +396 -61
- package/scripts/observability/query-proxy-history.mjs +34 -5
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { sanitizeForLog } from "../utils/logSanitize.js";
|
|
2
|
+
/** Shared provider terminal codes across native and SDK transports. */
|
|
3
|
+
export function classifyProxyFailureCode(code) {
|
|
4
|
+
if ([
|
|
5
|
+
"cyber_policy",
|
|
6
|
+
"content_policy_violation",
|
|
7
|
+
"content_filter",
|
|
8
|
+
"safety_violation",
|
|
9
|
+
].includes(code ?? "")) {
|
|
10
|
+
return { status: 403, retryable: false };
|
|
11
|
+
}
|
|
12
|
+
if ([
|
|
13
|
+
"max_output_tokens",
|
|
14
|
+
"context_length_exceeded",
|
|
15
|
+
"invalid_request_error",
|
|
16
|
+
"empty_response",
|
|
17
|
+
].includes(code ?? "")) {
|
|
18
|
+
return { status: undefined, retryable: false };
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
status: code === "rate_limit_exceeded" ? 429 : undefined,
|
|
22
|
+
retryable: undefined,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Read explicit SDK/API failure evidence without guessing from free-form text.
|
|
26
|
+
* Bounded traversal covers common error/cause/data and JSON response envelopes. */
|
|
27
|
+
export function getProxyUpstreamFailure(error) {
|
|
28
|
+
let status;
|
|
29
|
+
let code;
|
|
30
|
+
let retryable;
|
|
31
|
+
let message;
|
|
32
|
+
const queue = [error];
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
for (let index = 0; index < queue.length && index < 16; index++) {
|
|
35
|
+
const item = queue[index];
|
|
36
|
+
if (!item ||
|
|
37
|
+
typeof item !== "object" ||
|
|
38
|
+
Array.isArray(item) ||
|
|
39
|
+
seen.has(item)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
seen.add(item);
|
|
43
|
+
const value = item;
|
|
44
|
+
const candidateStatus = value.status ?? value.statusCode;
|
|
45
|
+
if (typeof candidateStatus === "number" &&
|
|
46
|
+
Number.isInteger(candidateStatus) &&
|
|
47
|
+
candidateStatus >= 400 &&
|
|
48
|
+
candidateStatus <= 599) {
|
|
49
|
+
status ??= candidateStatus;
|
|
50
|
+
}
|
|
51
|
+
if (code === undefined && typeof value.code === "string" && value.code) {
|
|
52
|
+
code = sanitizeForLog(value.code, 200);
|
|
53
|
+
if (typeof value.message === "string") {
|
|
54
|
+
message = sanitizeForLog(value.message, 500);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const candidateRetry = value.retryable ?? value.isRetryable;
|
|
58
|
+
if (candidateRetry === false) {
|
|
59
|
+
retryable = false;
|
|
60
|
+
}
|
|
61
|
+
else if (candidateRetry === true) {
|
|
62
|
+
retryable ??= true;
|
|
63
|
+
}
|
|
64
|
+
if (typeof value.message === "string" && message === undefined) {
|
|
65
|
+
message = sanitizeForLog(value.message, 500);
|
|
66
|
+
}
|
|
67
|
+
for (const key of ["error", "cause", "data", "response"]) {
|
|
68
|
+
if (value[key] !== undefined && queue.length < 16) {
|
|
69
|
+
queue.push(value[key]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (typeof value.responseBody === "string" &&
|
|
73
|
+
value.responseBody.length <= 65536 &&
|
|
74
|
+
queue.length < 16) {
|
|
75
|
+
try {
|
|
76
|
+
queue.push(JSON.parse(value.responseBody));
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
/* Plain text is not structured evidence. */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
84
|
+
status ??= 504;
|
|
85
|
+
}
|
|
86
|
+
if (status === undefined && code === undefined && retryable === undefined) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const known = classifyProxyFailureCode(code);
|
|
90
|
+
return {
|
|
91
|
+
status: status ?? known.status ?? 502,
|
|
92
|
+
code,
|
|
93
|
+
retryable: known.retryable ?? retryable,
|
|
94
|
+
message,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -378,6 +378,8 @@ function enqueueLifecycleEvent(input, onPersisted) {
|
|
|
378
378
|
const observedBodyBytes = nonNegativeInteger(input.observedBodyBytes);
|
|
379
379
|
const responseChunks = nonNegativeInteger(input.responseChunks);
|
|
380
380
|
const elapsedMs = finiteNonNegative(input.elapsedMs);
|
|
381
|
+
const requestTimeoutMs = finiteNonNegative(input.requestTimeoutMs);
|
|
382
|
+
const parentRequestId = clip(input.parentRequestId);
|
|
381
383
|
const model = clip(input.model);
|
|
382
384
|
const sessionHash = clip(input.sessionHash);
|
|
383
385
|
const terminalOutcome = clip(input.terminalOutcome);
|
|
@@ -391,6 +393,11 @@ function enqueueLifecycleEvent(input, onPersisted) {
|
|
|
391
393
|
sequence,
|
|
392
394
|
event: clip(input.event) ?? "unknown",
|
|
393
395
|
requestId: clip(input.requestId) ?? "unknown",
|
|
396
|
+
...(parentRequestId !== undefined ? { parentRequestId } : {}),
|
|
397
|
+
...(input.accountingScope !== undefined
|
|
398
|
+
? { accountingScope: input.accountingScope }
|
|
399
|
+
: {}),
|
|
400
|
+
...(requestTimeoutMs !== undefined ? { requestTimeoutMs } : {}),
|
|
394
401
|
method: clip(input.method) ?? "unknown",
|
|
395
402
|
path: clip(input.path) ?? "unknown",
|
|
396
403
|
...(model !== undefined ? { model } : {}),
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProxyBodyCaptureInput, ServerContext } from "../types/index.js";
|
|
2
|
+
/** Capture serialized client output and explicitly labelled SDK observations.
|
|
3
|
+
* This observer never reads a stream or creates a second consumer. */
|
|
4
|
+
export declare function createProxyRouteBodyCapture(ctx: ServerContext, model: string, stream: boolean, startedAt: number): {
|
|
5
|
+
log: (capture: ProxyBodyCaptureInput) => void;
|
|
6
|
+
request(): void;
|
|
7
|
+
json(body: unknown, responseStatus: number): void;
|
|
8
|
+
accumulator(phase: string, contentType: string, attempt?: number): {
|
|
9
|
+
append: (value: Uint8Array) => void;
|
|
10
|
+
appendEvent(value: unknown): void;
|
|
11
|
+
finish(responseStatus: number): void;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { isProxyBodyCaptureEnabled, logBodyCapture } from "./requestLogger.js";
|
|
2
|
+
const MAX_PREFIX_BYTES = 1024 * 1024;
|
|
3
|
+
const MAX_ACTIVE_PREFIX_BYTES = 16 * 1024 * 1024;
|
|
4
|
+
let activePrefixBytes = 0;
|
|
5
|
+
/** Capture serialized client output and explicitly labelled SDK observations.
|
|
6
|
+
* This observer never reads a stream or creates a second consumer. */
|
|
7
|
+
export function createProxyRouteBodyCapture(ctx, model, stream, startedAt) {
|
|
8
|
+
const log = (capture) => {
|
|
9
|
+
void logBodyCapture({
|
|
10
|
+
timestamp: new Date().toISOString(),
|
|
11
|
+
requestId: ctx.requestId,
|
|
12
|
+
model,
|
|
13
|
+
stream,
|
|
14
|
+
durationMs: Date.now() - startedAt,
|
|
15
|
+
...capture,
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
log,
|
|
20
|
+
request() {
|
|
21
|
+
log({
|
|
22
|
+
phase: "client_request",
|
|
23
|
+
headers: ctx.headers,
|
|
24
|
+
body: ctx.body,
|
|
25
|
+
contentType: "application/json",
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
json(body, responseStatus) {
|
|
29
|
+
log({
|
|
30
|
+
phase: "client_response",
|
|
31
|
+
headers: { "content-type": "application/json" },
|
|
32
|
+
body,
|
|
33
|
+
contentType: "application/json",
|
|
34
|
+
responseStatus,
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
accumulator(phase, contentType, attempt) {
|
|
38
|
+
const enabled = isProxyBodyCaptureEnabled();
|
|
39
|
+
const chunks = [];
|
|
40
|
+
let totalBytes = 0;
|
|
41
|
+
let retainedBytes = 0;
|
|
42
|
+
let truncated = false;
|
|
43
|
+
let finished = false;
|
|
44
|
+
const append = (value) => {
|
|
45
|
+
if (finished || !enabled) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
totalBytes += value.byteLength;
|
|
49
|
+
const remaining = Math.min(MAX_PREFIX_BYTES - retainedBytes, MAX_ACTIVE_PREFIX_BYTES - activePrefixBytes);
|
|
50
|
+
if (!truncated && remaining > 0) {
|
|
51
|
+
const prefix = Uint8Array.from(value.subarray(0, remaining));
|
|
52
|
+
chunks.push(prefix);
|
|
53
|
+
retainedBytes += prefix.byteLength;
|
|
54
|
+
activePrefixBytes += prefix.byteLength;
|
|
55
|
+
truncated = prefix.byteLength !== value.byteLength;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
truncated ||= value.byteLength > 0;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
append,
|
|
63
|
+
appendEvent(value) {
|
|
64
|
+
if (finished || !enabled) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
append(new TextEncoder().encode(`data: ${JSON.stringify(value) ?? "null"}\n\n`));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Diagnostic serialization cannot fail a provider request. Mark
|
|
72
|
+
// unavailable bytes as an explicitly incomplete capture instead.
|
|
73
|
+
truncated = true;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
finish(responseStatus) {
|
|
77
|
+
if (finished) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
finished = true;
|
|
81
|
+
if (!enabled) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const decoder = new TextDecoder();
|
|
85
|
+
const body = chunks
|
|
86
|
+
.map((chunk) => decoder.decode(chunk, { stream: true }))
|
|
87
|
+
.join("") + (truncated ? "" : decoder.decode());
|
|
88
|
+
activePrefixBytes -= retainedBytes;
|
|
89
|
+
chunks.length = 0;
|
|
90
|
+
log({
|
|
91
|
+
phase,
|
|
92
|
+
headers: { "content-type": contentType },
|
|
93
|
+
body,
|
|
94
|
+
bodySize: totalBytes,
|
|
95
|
+
sourceTruncated: truncated,
|
|
96
|
+
contentType,
|
|
97
|
+
responseStatus,
|
|
98
|
+
attempt,
|
|
99
|
+
...(phase.startsWith("sdk_")
|
|
100
|
+
? {
|
|
101
|
+
metadata: {
|
|
102
|
+
representation: "neurolink_sdk_events",
|
|
103
|
+
wireCapture: false,
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
: {}),
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ProxyTokenBudgetLease, ProxyTokenBudgetPolicy, ProxyTokenBudgetReservation, ProxyTokenBudgetRpcResponse, ProxyTokenBudgetSnapshot } from "../types/index.js";
|
|
2
|
+
/** Shared private session scope across every protocol and provider fallback. */
|
|
3
|
+
export declare function getProxyTokenBudgetSessionKey(headers: Pick<Headers, "get">): string;
|
|
4
|
+
/** Local admission failures must not be hidden by automatic provider fallback. */
|
|
5
|
+
export declare function getProxyTokenBudgetError(error: unknown): {
|
|
6
|
+
code: string;
|
|
7
|
+
status: 400 | 429 | 503;
|
|
8
|
+
retryable: false;
|
|
9
|
+
message: string;
|
|
10
|
+
} | null;
|
|
11
|
+
/** Invalid configured limits fail closed instead of silently disabling protection. */
|
|
12
|
+
export declare function parseProxyTokenBudgetPolicy(value: unknown): ProxyTokenBudgetPolicy;
|
|
13
|
+
export declare function getProxyTokenBudgetPolicy(): ProxyTokenBudgetPolicy | undefined;
|
|
14
|
+
/** One atomic coordinator per supervisor; requests never write budget state to disk. */
|
|
15
|
+
export declare class ProxyTokenBudgetCoordinator {
|
|
16
|
+
private readonly scope;
|
|
17
|
+
private readonly now;
|
|
18
|
+
private readonly maxKeys;
|
|
19
|
+
private readonly maxLeases;
|
|
20
|
+
private readonly buckets;
|
|
21
|
+
private readonly leases;
|
|
22
|
+
constructor(scope?: "process" | "supervisor", now?: () => number, maxKeys?: number, maxLeases?: number);
|
|
23
|
+
private bucket;
|
|
24
|
+
reserve(input: ProxyTokenBudgetReservation, owner?: string): {
|
|
25
|
+
leaseId: string;
|
|
26
|
+
snapshot: ProxyTokenBudgetSnapshot;
|
|
27
|
+
};
|
|
28
|
+
settle(leaseId: string, owner: string, actualTotalTokens?: number, cancelled?: boolean): ProxyTokenBudgetSnapshot | undefined;
|
|
29
|
+
ownerExited(owner: string): void;
|
|
30
|
+
}
|
|
31
|
+
/** Parent-side IPC hook is shared by every active and draining generation. */
|
|
32
|
+
export declare function handleProxyTokenBudgetMessage(value: unknown, owner: {
|
|
33
|
+
pid: number;
|
|
34
|
+
generation: number;
|
|
35
|
+
}, reply: (response: ProxyTokenBudgetRpcResponse) => void): boolean;
|
|
36
|
+
export declare function releaseProxyTokenBudgetOwner(pid: number, generation: number): void;
|
|
37
|
+
/** Reserve before each upstream attempt; unknown provider usage retains its estimate. */
|
|
38
|
+
export declare function reserveProxyTokenBudget(input: ProxyTokenBudgetReservation): Promise<ProxyTokenBudgetLease>;
|
|
39
|
+
/** Accounting acknowledgement loss must not turn delivered provider success into a stream failure. */
|
|
40
|
+
export declare function settleProxyTokenBudget(lease: ProxyTokenBudgetLease, actualTotalTokens?: number): Promise<void>;
|