@juspay/neurolink 12.12.6 → 12.12.7

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.
@@ -26,7 +26,7 @@ import { ProxyRuntimeConfigStore } from "../../proxy/runtimeConfig.js";
26
26
  import { startProxyLogCleanupScheduler } from "../../proxy/logCleanupScheduler.js";
27
27
  import { anthropicAccountKeysEqual, createAccountAllowlist, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, normalizeAnthropicAccountKey, shouldLoadFallbackCredential, } from "../../proxy/accountSelection.js";
28
28
  import { resolveProxyStatusAccountIdentity } from "../../proxy/codexAccountUsage.js";
29
- import { beginProxyRequest, getProxyActivitySnapshot, takeProxyResponseObservers, trackProxyResponse, } from "../../proxy/proxyActivity.js";
29
+ import { beginProxyRequest, getProxyActivitySnapshot, observeProxyFinalLog, takeProxyResponseObservers, trackProxyResponse, } from "../../proxy/proxyActivity.js";
30
30
  import { flushProxyLifecycleEvents, getProxyLifecycleLoggerSnapshot, hashProxyLifecycleSessionId, logProxyLifecycleEvent, } from "../../proxy/proxyLifecycle.js";
31
31
  import { describeInstallFailure, getGlobalInstallArgs, isTransientInstallFailure, resolveGlobalInstaller, validateInstalledVersion, } from "../../proxy/globalInstaller.js";
32
32
  import { startUpdaterWorkerSupervisor } from "../../proxy/updaterSupervisor.js";
@@ -1079,10 +1079,14 @@ function registerProxyRequestTracking(app, requestMetadata, readiness) {
1079
1079
  rejectForUpdate: readiness.drainingForUpdate,
1080
1080
  };
1081
1081
  requestMetadata.set(c.req.raw, metadata);
1082
+ const stopObservingFinalLog = observeProxyFinalLog(metadata.requestId, (entry) => {
1083
+ metadata.terminalResult = entry;
1084
+ });
1082
1085
  const finishActivity = metadata.rejectForUpdate
1083
1086
  ? () => undefined
1084
1087
  : beginProxyRequest();
1085
1088
  const finish = () => {
1089
+ stopObservingFinalLog();
1086
1090
  finishActivity();
1087
1091
  // Borrowed traffic holds a concurrency slot for the lifetime of the
1088
1092
  // response body, so it is released here rather than when the handler
@@ -1130,15 +1134,9 @@ function registerProxyRequestTracking(app, requestMetadata, readiness) {
1130
1134
  }
1131
1135
  }
1132
1136
  };
1133
- const notifyRouteTerminal = (details) => {
1134
- for (const observer of routeResponseObservers) {
1135
- try {
1136
- observer.onTerminal?.(details);
1137
- }
1138
- catch {
1139
- // Route-level accounting must never interfere with the relay.
1140
- }
1141
- }
1137
+ const notifyRouteTerminal = async (details) => {
1138
+ const results = await withTimeout(Promise.allSettled(routeResponseObservers.map(async (observer) => observer.onTerminal?.(details))), 2_000, "Timed out joining proxy response accounting");
1139
+ return results.some((result) => result.status === "rejected");
1142
1140
  };
1143
1141
  c.res = trackProxyResponse(c.res, finish, {
1144
1142
  onFirstChunk: ({ observedBodyBytes, responseChunks }) => {
@@ -1162,13 +1160,39 @@ function registerProxyRequestTracking(app, requestMetadata, readiness) {
1162
1160
  responseChunks,
1163
1161
  });
1164
1162
  },
1165
- onTerminal: ({ outcome, observedBodyBytes, responseChunks }) => {
1166
- if (outcome === "completed" &&
1167
- metadata.terminalErrorType === "stream_error") {
1168
- outcome = "stream_error";
1163
+ onTerminal: async ({ outcome, error, observedBodyBytes, responseChunks, }) => {
1164
+ const terminalMonotonicMs = performance.now();
1165
+ const terminalTimestampMs = Date.now();
1166
+ // Route accounting may await SSE parsing/cancellation. Join it before
1167
+ // publishing the semantic terminal record; transport EOF alone is not
1168
+ // evidence of a successful model response.
1169
+ let accountingTimedOut = false;
1170
+ let accountingFailed = false;
1171
+ try {
1172
+ accountingFailed = await notifyRouteTerminal({
1173
+ outcome,
1174
+ error,
1175
+ observedBodyBytes,
1176
+ responseChunks,
1177
+ });
1178
+ }
1179
+ catch {
1180
+ accountingTimedOut = true;
1169
1181
  }
1182
+ const final = metadata.terminalResult;
1183
+ const terminalOutcome = final?.terminalOutcome ??
1184
+ (outcome === "stream_error" ||
1185
+ metadata.terminalErrorType === "stream_error"
1186
+ ? "stream_error"
1187
+ : outcome === "client_cancelled"
1188
+ ? "client_cancelled"
1189
+ : responseStatus >= 400
1190
+ ? "handler_error"
1191
+ : "unknown");
1170
1192
  logProxyLifecycleEvent({
1171
1193
  event: "request_terminal",
1194
+ timestampMs: terminalTimestampMs,
1195
+ monotonicMs: terminalMonotonicMs,
1172
1196
  requestId: metadata.requestId,
1173
1197
  method: metadata.method,
1174
1198
  path: metadata.path,
@@ -1180,20 +1204,33 @@ function registerProxyRequestTracking(app, requestMetadata, readiness) {
1180
1204
  responseStatus,
1181
1205
  observedBodyBytes,
1182
1206
  responseChunks,
1183
- elapsedMs: performance.now() - startedMonotonicMs,
1184
- terminalOutcome: outcome,
1185
- errorType: metadata.terminalErrorType,
1186
- errorCode: metadata.terminalErrorCode,
1187
- });
1188
- notifyRouteTerminal({
1189
- outcome,
1190
- observedBodyBytes,
1191
- responseChunks,
1207
+ elapsedMs: terminalMonotonicMs - startedMonotonicMs,
1208
+ terminalOutcome,
1209
+ finalStatus: final?.responseStatus,
1210
+ transportOutcome: outcome,
1211
+ outcomeSource: final
1212
+ ? "final_request"
1213
+ : responseStatus >= 400
1214
+ ? "http_status"
1215
+ : terminalOutcome === "unknown"
1216
+ ? "unknown"
1217
+ : "transport_error",
1218
+ telemetryStatus: accountingTimedOut
1219
+ ? "timeout"
1220
+ : accountingFailed
1221
+ ? "observer_error"
1222
+ : final
1223
+ ? "complete"
1224
+ : "missing_final",
1225
+ errorType: final?.errorType ?? metadata.terminalErrorType,
1226
+ errorCode: final?.errorCode ?? metadata.terminalErrorCode,
1192
1227
  });
1228
+ stopObservingFinalLog();
1193
1229
  },
1194
1230
  });
1195
1231
  }
1196
1232
  catch (error) {
1233
+ stopObservingFinalLog();
1197
1234
  // Keep metadata available to app.onError, which records the client-facing
1198
1235
  // failure with the same request ID before deleting the WeakMap entry.
1199
1236
  finishActivity();
@@ -1238,7 +1275,7 @@ export async function createProxyStartApp(params) {
1238
1275
  const { createOpenAIProxyRoutes } = await import("../../server/routes/openaiProxyRoutes.js");
1239
1276
  const { createCodexProxyRoutes } = await import("../../server/routes/codexProxyRoutes.js");
1240
1277
  const { createGeminiProxyRoutes } = await import("../../server/routes/geminiProxyRoutes.js");
1241
- const { logBodyCapture, logRequest } = await import("../../proxy/requestLogger.js");
1278
+ const { logBodyCapture, logRequest, getRequestLoggerSnapshot } = await import("../../proxy/requestLogger.js");
1242
1279
  const { recordFinalError } = await import("../../proxy/usageStats.js");
1243
1280
  const { admitInboundShareRequest, isGrantRequiredByEnv } = await import("../../proxy/shareGate.js");
1244
1281
  const { runWithShareContext } = await import("../../proxy/shareContext.js");
@@ -1978,6 +2015,7 @@ export async function createProxyStartApp(params) {
1978
2015
  })(),
1979
2016
  observability: {
1980
2017
  lifecycle: getProxyLifecycleLoggerSnapshot(),
2018
+ requestLogs: getRequestLoggerSnapshot(),
1981
2019
  },
1982
2020
  autoUpdate: {
1983
2021
  enabled: isProxyAutoUpdateEnabled(),
@@ -70,6 +70,7 @@ function printAnalysis(report) {
70
70
  for (const [label, summary] of [
71
71
  ["Response headers", report.latencyMs.headers],
72
72
  ["First chunk", report.latencyMs.firstChunk],
73
+ ["First useful output", report.latencyMs.firstUsefulOutput],
73
74
  ["Terminal", report.latencyMs.terminal],
74
75
  ["Final request log", report.latencyMs.finalRequest],
75
76
  ["Account attempt", report.latencyMs.attempt],
@@ -100,7 +101,9 @@ function printAnalysis(report) {
100
101
  if (!report.coverage.comparableRequestAttempts) {
101
102
  logger.always(chalk.yellow(" WARNING: request and attempt totals do not cover a comparable full window; do not reconcile them as one cohort"));
102
103
  }
103
- 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`);
104
+ 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 (${report.dataQuality.conflictingLifecycleDuplicates} conflicting)`);
105
+ logger.always(` Outcome evidence: ${report.dataQuality.finalOutcomeConflicts} conflicts reconciled, ${report.dataQuality.acceptedWithoutFinal} accepted without a final record, ${report.dataQuality.terminalWithoutFinal} transport terminals without a final record`);
106
+ logger.always(` Repeated attempt records merged: ${report.dataQuality.duplicateAttempts}`);
104
107
  logger.always(` Routing decisions: ${report.dataQuality.routingDecisions.valid} valid, ${report.dataQuality.routingDecisions.invalid} invalid, ${report.dataQuality.routingDecisions.absent} absent`);
105
108
  for (const [stream, range] of Object.entries(report.dataQuality.streams)) {
106
109
  if (range.observedFrom) {
@@ -39,7 +39,7 @@
39
39
  * `prompt_tokens`/`completion_tokens` spellings. A `null` result means "not
40
40
  * observed", never "zero tokens".
41
41
  */
42
- import type { CodexStreamUsage } from "../types/index.js";
42
+ import type { CodexStreamUsage, CodexStreamEvidence } from "../types/index.js";
43
43
  /**
44
44
  * Pull usage out of one parsed SSE `data:` payload.
45
45
  *
@@ -65,4 +65,5 @@ export declare function scanCodexSSEForUsage(text: string): CodexStreamUsage | n
65
65
  export declare function createCodexUsageTap(): {
66
66
  stream: TransformStream<Uint8Array, Uint8Array>;
67
67
  usage: Promise<CodexStreamUsage | null>;
68
+ evidence: () => CodexStreamEvidence;
68
69
  };
@@ -40,6 +40,8 @@
40
40
  * observed", never "zero tokens".
41
41
  */
42
42
  import { appendFileSync } from "node:fs";
43
+ import { extractSSEEvents } from "./sseInterceptor.js";
44
+ import { sanitizeForLog } from "../utils/logSanitize.js";
43
45
  const nonNegativeInt = (value) => typeof value === "number" && Number.isFinite(value) && value > 0
44
46
  ? Math.floor(value)
45
47
  : 0;
@@ -161,6 +163,68 @@ function createCaptureSink() {
161
163
  * none was. It never rejects.
162
164
  */
163
165
  export function createCodexUsageTap() {
166
+ const evidence = { completed: false, terminalBytes: 0 };
167
+ let totalBytes = 0;
168
+ const inspectEvidence = (events) => {
169
+ for (const frame of events) {
170
+ try {
171
+ const event = JSON.parse(frame.data);
172
+ if (!event || typeof event !== "object") {
173
+ continue;
174
+ }
175
+ const seen = extractCodexUsage(event);
176
+ if (seen) {
177
+ latest = seen;
178
+ }
179
+ const type = event.type ?? frame.event;
180
+ if ((type === "response.output_text.delta" ||
181
+ type === "response.function_call_arguments.delta") &&
182
+ typeof event.delta === "string" &&
183
+ event.delta.length > 0) {
184
+ evidence.firstUsefulOutputAt ??= Date.now();
185
+ }
186
+ if (type === "response.completed") {
187
+ evidence.completed = true;
188
+ evidence.terminalBytes = totalBytes;
189
+ }
190
+ else if (type === "error" ||
191
+ type === "response.failed" ||
192
+ type === "response.incomplete") {
193
+ evidence.errorType = "stream_error";
194
+ const response = event.response;
195
+ const details = response && typeof response === "object"
196
+ ? response
197
+ : event;
198
+ const rawError = details.error;
199
+ const error = rawError && typeof rawError === "object"
200
+ ? rawError
201
+ : details;
202
+ const incomplete = details.incomplete_details;
203
+ const reason = incomplete &&
204
+ typeof incomplete === "object" &&
205
+ "reason" in incomplete
206
+ ? incomplete.reason
207
+ : undefined;
208
+ evidence.errorCode =
209
+ typeof error.code === "string"
210
+ ? sanitizeForLog(error.code).slice(0, 200)
211
+ : typeof reason === "string"
212
+ ? sanitizeForLog(reason).slice(0, 200)
213
+ : String(type);
214
+ evidence.errorMessage =
215
+ typeof error.message === "string"
216
+ ? sanitizeForLog(error.message).slice(0, 200)
217
+ : type === "response.incomplete"
218
+ ? "Codex reported an incomplete response"
219
+ : "Codex reported a stream failure";
220
+ evidence.terminalBytes = totalBytes;
221
+ }
222
+ }
223
+ catch {
224
+ // Unknown frames cannot establish successful completion.
225
+ }
226
+ }
227
+ };
164
228
  let settleUsage = () => { };
165
229
  const usage = new Promise((resolve) => {
166
230
  settleUsage = resolve;
@@ -179,40 +243,33 @@ export function createCodexUsageTap() {
179
243
  const capture = createCaptureSink();
180
244
  let carry = "";
181
245
  let latest = null;
182
- /**
183
- * Ceiling on the unterminated tail we are willing to hold.
184
- *
185
- * `carry` normally holds a fraction of one SSE line, because every newline
186
- * flushes it. A stream that never sends one — a hung upstream, a
187
- * non-SSE body relayed by mistake — would otherwise grow it without bound
188
- * for the life of the request. One `response.completed` event is a few
189
- * hundred bytes, so a megabyte is far past any real event, and dropping the
190
- * tail costs at most the usage reading this tap is allowed to miss anyway.
191
- */
246
+ // Bound malformed unterminated events without ever withholding relay bytes.
192
247
  const CARRY_LIMIT_CHARS = 1024 * 1024;
248
+ let discardingEvent = false;
193
249
  const transformer = {
194
250
  transform(chunk, controller) {
195
251
  // Bytes go out first and unconditionally: nothing below can delay or
196
252
  // alter what the client receives.
197
253
  controller.enqueue(chunk);
254
+ totalBytes += chunk.byteLength;
198
255
  try {
199
256
  capture?.(chunk);
200
257
  carry += decoder.decode(chunk, { stream: true });
201
- // Keep only the trailing partial line; events are newline-delimited.
202
- const lastBreak = carry.lastIndexOf("\n");
203
- if (lastBreak === -1) {
204
- if (carry.length > CARRY_LIMIT_CHARS) {
205
- // No line break in a megabyte: this is not the SSE stream we can
206
- // read. Give up on the tail rather than grow forever.
207
- carry = "";
258
+ if (discardingEvent) {
259
+ const boundary = /\r\n\r\n|\n\n|\r\r/.exec(carry);
260
+ if (!boundary) {
261
+ carry = carry.slice(-3);
262
+ return;
208
263
  }
209
- return;
264
+ carry = carry.slice(boundary.index + boundary[0].length);
265
+ discardingEvent = false;
210
266
  }
211
- const complete = carry.slice(0, lastBreak);
212
- carry = carry.slice(lastBreak + 1);
213
- const seen = scanCodexSSEForUsage(complete);
214
- if (seen) {
215
- latest = seen;
267
+ const { events, remainder } = extractSSEEvents(carry);
268
+ carry = remainder;
269
+ inspectEvidence(events);
270
+ if (carry.length > CARRY_LIMIT_CHARS) {
271
+ carry = carry.slice(-2);
272
+ discardingEvent = true;
216
273
  }
217
274
  }
218
275
  catch {
@@ -220,15 +277,7 @@ export function createCodexUsageTap() {
220
277
  }
221
278
  },
222
279
  flush() {
223
- try {
224
- const seen = scanCodexSSEForUsage(carry);
225
- if (seen) {
226
- latest = seen;
227
- }
228
- }
229
- catch {
230
- // ignored — see above
231
- }
280
+ // An event without its dispatch delimiter is incomplete on the wire.
232
281
  settle(latest);
233
282
  },
234
283
  /**
@@ -242,5 +291,5 @@ export function createCodexUsageTap() {
242
291
  },
243
292
  };
244
293
  const stream = new TransformStream(transformer);
245
- return { stream, usage };
294
+ return { stream, usage, evidence: () => ({ ...evidence }) };
246
295
  }
@@ -1,4 +1,7 @@
1
- import type { ProxyActivitySnapshot, ProxyResponseTrackingObserver } from "../types/index.js";
1
+ import type { ProxyActivitySnapshot, ProxyResponseTrackingObserver, RequestLogEntry } from "../types/index.js";
2
+ /** Join route accounting to the HTTP lifecycle without relying on write order. */
3
+ export declare function observeProxyFinalLog(requestId: string, observer: (entry: RequestLogEntry) => void): () => void;
4
+ export declare function notifyProxyFinalLog(entry: RequestLogEntry): void;
2
5
  export declare function registerProxyResponseObserver(metadata: object, observer: ProxyResponseTrackingObserver): void;
3
6
  export declare function takeProxyResponseObservers(metadata: object): ProxyResponseTrackingObserver[];
4
7
  /** Track one client-facing proxy request until its response body settles. */
@@ -8,6 +8,19 @@ let lastActivityAtMs = null;
8
8
  // response through one tracker, which fans these observers out at the point
9
9
  // where bytes actually leave the proxy.
10
10
  const responseObserversByMetadata = new WeakMap();
11
+ const finalLogObservers = new Map();
12
+ /** Join route accounting to the HTTP lifecycle without relying on write order. */
13
+ export function observeProxyFinalLog(requestId, observer) {
14
+ finalLogObservers.set(requestId, observer);
15
+ return () => {
16
+ if (finalLogObservers.get(requestId) === observer) {
17
+ finalLogObservers.delete(requestId);
18
+ }
19
+ };
20
+ }
21
+ export function notifyProxyFinalLog(entry) {
22
+ finalLogObservers.get(entry.requestId)?.(entry);
23
+ }
11
24
  export function registerProxyResponseObserver(metadata, observer) {
12
25
  const existing = responseObserversByMetadata.get(metadata);
13
26
  if (existing) {
@@ -69,12 +82,17 @@ export function isProxyActivityQuiet(snapshot, quietThresholdMs, nowMs = Date.no
69
82
  /** Keep activity open until the response body completes, errors, or is cancelled. */
70
83
  export function trackProxyResponse(response, finishRequest, observer) {
71
84
  if (!response.body) {
72
- finishRequest();
73
- safelyNotifyObserver(() => observer?.onTerminal?.({
74
- outcome: "bodyless",
75
- observedBodyBytes: 0,
76
- responseChunks: 0,
77
- }));
85
+ try {
86
+ const notified = observer?.onTerminal?.({
87
+ outcome: "bodyless",
88
+ observedBodyBytes: 0,
89
+ responseChunks: 0,
90
+ });
91
+ void Promise.resolve(notified).then(finishRequest, finishRequest);
92
+ }
93
+ catch {
94
+ finishRequest();
95
+ }
78
96
  return response;
79
97
  }
80
98
  const reader = response.body.getReader();
@@ -88,17 +106,25 @@ export function trackProxyResponse(response, finishRequest, observer) {
88
106
  void reader.closed.then(() => {
89
107
  sourceClosed = true;
90
108
  }, () => undefined);
91
- const settle = (outcome) => {
109
+ const settle = (outcome, error) => {
92
110
  if (settled) {
93
111
  return;
94
112
  }
95
113
  settled = true;
96
- finishRequest();
97
- safelyNotifyObserver(() => observer?.onTerminal?.({
98
- outcome,
99
- observedBodyBytes,
100
- responseChunks,
101
- }));
114
+ // Keep drain accounting open through bounded terminal bookkeeping, but
115
+ // never hold back the client's response body while telemetry is written.
116
+ try {
117
+ const notified = observer?.onTerminal?.({
118
+ outcome,
119
+ error,
120
+ observedBodyBytes,
121
+ responseChunks,
122
+ });
123
+ void Promise.resolve(notified).then(finishRequest, finishRequest);
124
+ }
125
+ catch {
126
+ finishRequest();
127
+ }
102
128
  };
103
129
  const trackedBody = new ReadableStream({
104
130
  async pull(controller) {
@@ -120,7 +146,7 @@ export function trackProxyResponse(response, finishRequest, observer) {
120
146
  }
121
147
  }
122
148
  catch (error) {
123
- settle("stream_error");
149
+ settle("stream_error", error);
124
150
  controller.error(error);
125
151
  }
126
152
  },