@juspay/neurolink 12.14.7 → 12.14.8

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.
@@ -15,6 +15,7 @@
15
15
  * upstreamResponse.body.pipeThrough(stream).pipeTo(clientWritable);
16
16
  * const data = await telemetry; // resolves on stream end
17
17
  */
18
+ import { hasUsefulClaudeContent } from "./claudeOutputObservation.js";
18
19
  // ---------------------------------------------------------------------------
19
20
  // Constants
20
21
  // ---------------------------------------------------------------------------
@@ -172,6 +173,8 @@ function finalize(acc) {
172
173
  messageId: acc.messageId,
173
174
  messageStopReceived: acc.messageStopReceived,
174
175
  firstUsefulOutputAt: acc.firstUsefulOutputAt,
176
+ firstUsefulOutputEvent: acc.firstUsefulOutputEvent,
177
+ observationIncomplete: acc.observationIncomplete,
175
178
  model: acc.model,
176
179
  usage: {
177
180
  inputTokens: acc.inputTokens,
@@ -191,6 +194,7 @@ function finalize(acc) {
191
194
  ? { streamErrorMessage: acc.streamErrorMessage }
192
195
  : {}),
193
196
  ...(acc.rawTextChunks ? { rawText: acc.rawTextChunks.join("") } : {}),
197
+ rawTextTruncated: acc.rawTextTruncated,
194
198
  };
195
199
  }
196
200
  // ---------------------------------------------------------------------------
@@ -320,12 +324,17 @@ function processEvent(acc, event) {
320
324
  if (!event.data) {
321
325
  return;
322
326
  }
327
+ if (event.data.length > 1024 * 1024) {
328
+ acc.observationIncomplete = true;
329
+ return;
330
+ }
323
331
  let parsed;
324
332
  try {
325
333
  parsed = JSON.parse(event.data);
326
334
  }
327
335
  catch {
328
336
  // Malformed JSON — skip silently, bytes already forwarded to client
337
+ acc.observationIncomplete = true;
329
338
  return;
330
339
  }
331
340
  const payloadType = parsed && typeof parsed === "object" && "type" in parsed
@@ -357,11 +366,52 @@ function processEvent(acc, event) {
357
366
  typeof delta === "object" &&
358
367
  (("text" in delta &&
359
368
  typeof delta.text === "string" &&
360
- delta.text.length > 0) ||
369
+ delta.text.trim().length > 0) ||
361
370
  ("partial_json" in delta &&
362
371
  typeof delta.partial_json === "string" &&
363
- delta.partial_json.length > 0))) {
364
- acc.firstUsefulOutputAt ??= Date.now();
372
+ delta.partial_json.trim().length > 0) ||
373
+ ("refusal" in delta &&
374
+ typeof delta.refusal === "string" &&
375
+ delta.refusal.trim().length > 0))) {
376
+ if (acc.firstUsefulOutputAt === undefined) {
377
+ acc.firstUsefulOutputAt = now;
378
+ acc.firstUsefulOutputEvent = `content_block_delta.${"text" in delta ? "text" : "refusal" in delta ? "refusal" : "partial_json"}`;
379
+ }
380
+ }
381
+ }
382
+ if (acc.firstUsefulOutputAt === undefined &&
383
+ parsed &&
384
+ typeof parsed === "object") {
385
+ const payload = parsed;
386
+ if (eventType === "message_start" &&
387
+ hasUsefulClaudeContent(payload.message)) {
388
+ acc.firstUsefulOutputAt = now;
389
+ acc.firstUsefulOutputEvent = "message_start.content";
390
+ }
391
+ const block = payload.content_block;
392
+ if (eventType === "content_block_start" &&
393
+ block &&
394
+ typeof block === "object") {
395
+ const content = block;
396
+ if ((typeof content.text === "string" && content.text.trim().length > 0) ||
397
+ (typeof content.refusal === "string" &&
398
+ content.refusal.trim().length > 0) ||
399
+ ((content.type === "tool_use" || content.type === "server_tool_use") &&
400
+ content.input &&
401
+ typeof content.input === "object" &&
402
+ Object.keys(content.input).length > 0)) {
403
+ acc.firstUsefulOutputAt = now;
404
+ acc.firstUsefulOutputEvent = "content_block_start";
405
+ }
406
+ }
407
+ // A completed zero-argument tool call is useful even when no input delta was sent.
408
+ if (eventType === "content_block_stop" &&
409
+ typeof payload.index === "number") {
410
+ const content = acc.contentBlocks.find((block) => block.index === payload.index);
411
+ if (content?.type === "tool_use" && content.toolName) {
412
+ acc.firstUsefulOutputAt = now;
413
+ acc.firstUsefulOutputEvent = "content_block_stop.tool_use";
414
+ }
365
415
  }
366
416
  }
367
417
  switch (eventType) {
@@ -403,6 +453,7 @@ export function createSSEInterceptor(options = {}) {
403
453
  const captureRawText = options.captureRawText ?? false;
404
454
  const acc = createAccumulator(captureRawText);
405
455
  let sseBuffer = "";
456
+ let discardingFrame = false;
406
457
  let resolved = false;
407
458
  const decoder = new TextDecoder();
408
459
  let resolveTelemetry;
@@ -415,6 +466,9 @@ export function createSSEInterceptor(options = {}) {
415
466
  return;
416
467
  }
417
468
  resolved = true;
469
+ if (sseBuffer.trim()) {
470
+ acc.observationIncomplete = true;
471
+ }
418
472
  resolveTelemetry(finalize(acc));
419
473
  }
420
474
  const transform = new TransformStream({
@@ -427,8 +481,22 @@ export function createSSEInterceptor(options = {}) {
427
481
  const decodedChunk = decoder.decode(chunk, { stream: true });
428
482
  appendRawTextChunk(acc, decodedChunk);
429
483
  sseBuffer += decodedChunk;
484
+ if (discardingFrame) {
485
+ const boundary = /\r\n\r\n|\n\n|\r\r/.exec(sseBuffer);
486
+ if (!boundary) {
487
+ sseBuffer = sseBuffer.slice(-4);
488
+ return;
489
+ }
490
+ sseBuffer = sseBuffer.slice(boundary.index + boundary[0].length);
491
+ discardingFrame = false;
492
+ }
430
493
  const { events, remainder } = extractSSEEvents(sseBuffer);
431
494
  sseBuffer = remainder;
495
+ if (sseBuffer.length > 1024 * 1024) {
496
+ acc.observationIncomplete = true;
497
+ discardingFrame = true;
498
+ sseBuffer = sseBuffer.slice(-4);
499
+ }
432
500
  for (const event of events) {
433
501
  processEvent(acc, event);
434
502
  }
@@ -1,14 +1,3 @@
1
- /**
2
- * Claude-Compatible Proxy Routes
3
- *
4
- * Exposes Anthropic-compatible /v1/messages, /v1/models, and /v1/messages/count_tokens
5
- * endpoints. ALL requests are routed through ctx.neurolink.generate() / ctx.neurolink.stream()
6
- * -- no direct HTTP calls to Anthropic.
7
- *
8
- * An optional ModelRouter can remap incoming model names to different
9
- * provider/model pairs (e.g. "claude-sonnet-4-20250514" -> vertex/gemini-2.5-pro).
10
- * Without a router, models are passed through to the Anthropic provider.
11
- */
12
1
  import { buildTranslationOptions } from "../../proxy/proxyTranslationEngine.js";
13
2
  import { ProxyTracer } from "../../proxy/proxyTracer.js";
14
3
  import { isPermanentRefreshFailure } from "../../proxy/tokenRefresh.js";
@@ -1,3 +1,4 @@
1
+ import { claudeOutputLogFields, observeClaudeJsonOutput, observeClaudeStreamOutput, } from "../../proxy/claudeOutputObservation.js";
1
2
  /**
2
3
  * Claude-Compatible Proxy Routes
3
4
  *
@@ -2437,7 +2438,7 @@ async function handleClaudePassthroughStreamResponse(args) {
2437
2438
  ])
2438
2439
  .then(([data, clientBody, rawOutcome]) => {
2439
2440
  const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage, data.messageStopReceived);
2440
- ctx.metadata.firstUsefulOutputAt = data.firstUsefulOutputAt;
2441
+ observeClaudeStreamOutput(ctx.metadata, data);
2441
2442
  const failure = getStreamFailureDetails(terminalOutcome);
2442
2443
  capturedTracer.setUsage({
2443
2444
  inputTokens: data.usage.inputTokens,
@@ -2482,6 +2483,7 @@ async function handleClaudePassthroughStreamResponse(args) {
2482
2483
  phase: "upstream_response",
2483
2484
  headers: responseHeaders,
2484
2485
  body: data.rawText ?? "",
2486
+ sourceTruncated: data.rawTextTruncated,
2485
2487
  bodySize: data.totalBytesReceived,
2486
2488
  contentType: responseHeaders["content-type"] ?? "text/event-stream",
2487
2489
  account: "passthrough",
@@ -2494,6 +2496,7 @@ async function handleClaudePassthroughStreamResponse(args) {
2494
2496
  phase: "client_response",
2495
2497
  headers: responseHeaders,
2496
2498
  body: clientBody.text,
2499
+ sourceTruncated: clientBody.truncated,
2497
2500
  bodySize: clientBody.totalBytes,
2498
2501
  contentType: responseHeaders["content-type"] ?? "text/event-stream",
2499
2502
  account: "passthrough",
@@ -2539,7 +2542,7 @@ async function handleClaudePassthroughStreamResponse(args) {
2539
2542
  ])
2540
2543
  .then(([data, clientBody, rawOutcome]) => {
2541
2544
  const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage, data.messageStopReceived);
2542
- ctx.metadata.firstUsefulOutputAt = data.firstUsefulOutputAt;
2545
+ observeClaudeStreamOutput(ctx.metadata, data);
2543
2546
  const failure = getStreamFailureDetails(terminalOutcome);
2544
2547
  finalizeStream(failure?.status ?? response.status, failure?.errorType, failure?.message, {
2545
2548
  inputTokens: data.usage.inputTokens,
@@ -2551,6 +2554,7 @@ async function handleClaudePassthroughStreamResponse(args) {
2551
2554
  phase: "upstream_response",
2552
2555
  headers: responseHeaders,
2553
2556
  body: data.rawText ?? "",
2557
+ sourceTruncated: data.rawTextTruncated,
2554
2558
  bodySize: data.totalBytesReceived,
2555
2559
  contentType: responseHeaders["content-type"] ?? "text/event-stream",
2556
2560
  account: "passthrough",
@@ -2563,6 +2567,7 @@ async function handleClaudePassthroughStreamResponse(args) {
2563
2567
  phase: "client_response",
2564
2568
  headers: responseHeaders,
2565
2569
  body: clientBody.text,
2570
+ sourceTruncated: clientBody.truncated,
2566
2571
  bodySize: clientBody.totalBytes,
2567
2572
  contentType: responseHeaders["content-type"] ?? "text/event-stream",
2568
2573
  account: "passthrough",
@@ -2603,7 +2608,7 @@ async function handleClaudePassthroughStreamResponse(args) {
2603
2608
  });
2604
2609
  }
2605
2610
  async function handleClaudePassthroughJsonResponse(args) {
2606
- const { bodyStr, response, tracer, requestStartTime, upstreamSpan, upstreamResponseHeaders, logProxyBody, logFinalRequest, } = args;
2611
+ const { ctx, bodyStr, response, tracer, requestStartTime, upstreamSpan, upstreamResponseHeaders, logProxyBody, logFinalRequest, } = args;
2607
2612
  const responseText = await response.text();
2608
2613
  tracer?.logUpstreamResponseBody(responseText);
2609
2614
  logProxyBody({
@@ -2631,6 +2636,7 @@ async function handleClaudePassthroughJsonResponse(args) {
2631
2636
  durationMs: Date.now() - requestStartTime,
2632
2637
  });
2633
2638
  const responseJson = JSON.parse(responseText);
2639
+ observeClaudeJsonOutput(ctx.metadata, responseJson);
2634
2640
  if (tracer && responseJson && typeof responseJson === "object") {
2635
2641
  const usage = responseJson.usage;
2636
2642
  if (usage) {
@@ -3569,6 +3575,8 @@ async function executeClaudeFallbackTranslation(args) {
3569
3575
  for (const frame of serializer.finish(resolvedUsage.output, reason)) {
3570
3576
  frames.push(frame);
3571
3577
  }
3578
+ ctx.metadata.firstUsefulOutputAt = Date.now();
3579
+ ctx.metadata.firstUsefulOutputEvent = "translated_response.ready";
3572
3580
  // Telemetry AFTER validation — not before like the old lazy path
3573
3581
  tracer?.end(200, Date.now() - requestStartTime);
3574
3582
  logFinalRequest(200, "", providerLabel, undefined, undefined, {
@@ -3609,6 +3617,7 @@ async function executeClaudeFallbackTranslation(args) {
3609
3617
  };
3610
3618
  tracer?.end(200, Date.now() - requestStartTime);
3611
3619
  const clientResponse = serializeClaudeResponse(internal, body.model);
3620
+ observeClaudeJsonOutput(ctx.metadata, clientResponse);
3612
3621
  logFinalRequest(200, "", providerLabel, undefined, undefined, {
3613
3622
  inputTokens: internal.usage?.input,
3614
3623
  outputTokens: internal.usage?.output,
@@ -3855,6 +3864,7 @@ async function executeClaudeCodexFallback(args) {
3855
3864
  };
3856
3865
  tracer?.end(200, Date.now() - requestStartTime);
3857
3866
  const clientResponse = serializeClaudeResponse(internal, body.model);
3867
+ observeClaudeJsonOutput(ctx.metadata, clientResponse);
3858
3868
  logFinalRequest(200, accountLabel, accountType, undefined, undefined, {
3859
3869
  inputTokens: parsed.usage?.input,
3860
3870
  outputTokens: parsed.usage?.output,
@@ -4476,6 +4486,7 @@ async function handleAnthropicSuccessfulResponse(args) {
4476
4486
  });
4477
4487
  }
4478
4488
  return handleAnthropicJsonSuccessResponse({
4489
+ ctx,
4479
4490
  account,
4480
4491
  response,
4481
4492
  responseHeaders,
@@ -4758,7 +4769,7 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4758
4769
  telemetryDone = Promise.all([telemetry, clientCapture, streamOutcome])
4759
4770
  .then(([data, clientBody, rawOutcome]) => {
4760
4771
  const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage, data.messageStopReceived);
4761
- ctx.metadata.firstUsefulOutputAt = data.firstUsefulOutputAt;
4772
+ observeClaudeStreamOutput(ctx.metadata, data);
4762
4773
  recordCommittedAnthropicStreamAttemptFailure(terminalOutcome, account, logAttempt);
4763
4774
  capturedTracer.setUsage({
4764
4775
  inputTokens: data.usage.inputTokens,
@@ -4823,6 +4834,7 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4823
4834
  phase: "upstream_response",
4824
4835
  headers: responseHeaders,
4825
4836
  body: data.rawText ?? "",
4837
+ sourceTruncated: data.rawTextTruncated,
4826
4838
  bodySize: data.totalBytesReceived,
4827
4839
  contentType: responseHeaders["content-type"] ?? "text/event-stream",
4828
4840
  account: capturedAccountLabel,
@@ -4835,6 +4847,7 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4835
4847
  phase: "client_response",
4836
4848
  headers: responseHeaders,
4837
4849
  body: clientBody.text,
4850
+ sourceTruncated: clientBody.truncated,
4838
4851
  bodySize: clientBody.totalBytes,
4839
4852
  contentType: responseHeaders["content-type"] ?? "text/event-stream",
4840
4853
  account: capturedAccountLabel,
@@ -4888,7 +4901,7 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4888
4901
  ])
4889
4902
  .then(([data, clientBody, rawOutcome]) => {
4890
4903
  const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage, data.messageStopReceived);
4891
- ctx.metadata.firstUsefulOutputAt = data.firstUsefulOutputAt;
4904
+ observeClaudeStreamOutput(ctx.metadata, data);
4892
4905
  recordCommittedAnthropicStreamAttemptFailure(terminalOutcome, account, logAttempt);
4893
4906
  const failure = getStreamFailureDetails(terminalOutcome);
4894
4907
  const usage = {
@@ -4911,6 +4924,7 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4911
4924
  phase: "upstream_response",
4912
4925
  headers: responseHeaders,
4913
4926
  body: data.rawText ?? "",
4927
+ sourceTruncated: data.rawTextTruncated,
4914
4928
  bodySize: data.totalBytesReceived,
4915
4929
  contentType: responseHeaders["content-type"] ?? "text/event-stream",
4916
4930
  account: capturedAccountLabel,
@@ -4923,6 +4937,7 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4923
4937
  phase: "client_response",
4924
4938
  headers: responseHeaders,
4925
4939
  body: clientBody.text,
4940
+ sourceTruncated: clientBody.truncated,
4926
4941
  bodySize: clientBody.totalBytes,
4927
4942
  contentType: responseHeaders["content-type"] ?? "text/event-stream",
4928
4943
  account: capturedAccountLabel,
@@ -4944,6 +4959,7 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4944
4959
  phase: "client_response",
4945
4960
  headers: responseHeaders,
4946
4961
  body: clientBody.text,
4962
+ sourceTruncated: clientBody.truncated,
4947
4963
  bodySize: clientBody.totalBytes,
4948
4964
  contentType: responseHeaders["content-type"] ?? "text/event-stream",
4949
4965
  account: account.label,
@@ -4995,7 +5011,7 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4995
5011
  };
4996
5012
  }
4997
5013
  async function handleAnthropicJsonSuccessResponse(args) {
4998
- const { account, response, responseHeaders, tracer, requestStartTime, fetchStartMs, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, logFinalRequest, } = args;
5014
+ const { ctx, account, response, responseHeaders, tracer, requestStartTime, fetchStartMs, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, logFinalRequest, } = args;
4999
5015
  const responseText = await response.text();
5000
5016
  logAttempt(response.status, undefined, undefined, {
5001
5017
  attemptDurationMs: Date.now() - fetchStartMs,
@@ -5026,6 +5042,7 @@ async function handleAnthropicJsonSuccessResponse(args) {
5026
5042
  durationMs: Date.now() - requestStartTime,
5027
5043
  });
5028
5044
  const responseJson = JSON.parse(responseText);
5045
+ observeClaudeJsonOutput(ctx.metadata, responseJson);
5029
5046
  // Settlement is not diagnostics. It ran inside the tracer branch, so a node
5030
5047
  // with tracing off served every borrowed request for free and the lender's
5031
5048
  // ledger never moved — settle from the response itself instead.
@@ -5087,7 +5104,7 @@ async function handleAnthropicJsonSuccessResponse(args) {
5087
5104
  return { response: responseJson };
5088
5105
  }
5089
5106
  async function handleAnthropicSuccessfulNonStreamRetryResponse(args) {
5090
- const { account, accountState, requestedModel, retryResp, tracer, requestStartTime, fetchStartMs, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, logFinalRequest, } = args;
5107
+ const { ctx, account, accountState, requestedModel, retryResp, tracer, requestStartTime, fetchStartMs, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, logFinalRequest, } = args;
5091
5108
  const retryQuota = parseQuotaHeaders(retryResp.headers, {
5092
5109
  model: requestedModel,
5093
5110
  });
@@ -5145,6 +5162,7 @@ async function handleAnthropicSuccessfulNonStreamRetryResponse(args) {
5145
5162
  durationMs: Date.now() - requestStartTime,
5146
5163
  });
5147
5164
  const retryJson = JSON.parse(retryText);
5165
+ observeClaudeJsonOutput(ctx.metadata, retryJson);
5148
5166
  // A response served after an auth retry is a served response: it costs the
5149
5167
  // lender's account exactly what any other one does.
5150
5168
  settleFromResponseUsage(account, retryJson);
@@ -5267,6 +5285,7 @@ async function handleAnthropicAuthRetry(args) {
5267
5285
  })
5268
5286
  : {
5269
5287
  response: await handleAnthropicSuccessfulNonStreamRetryResponse({
5288
+ ctx,
5270
5289
  account,
5271
5290
  accountState,
5272
5291
  requestedModel: body.model,
@@ -6004,11 +6023,7 @@ function createClaudeRequestRuntimeContext(args) {
6004
6023
  ...buildClientAttribution(ctx.headers),
6005
6024
  responseStatus: status,
6006
6025
  responseTimeMs: Date.now() - requestStartTime,
6007
- ...(typeof ctx.metadata.firstUsefulOutputAt === "number"
6008
- ? {
6009
- firstUsefulOutputMs: Math.max(0, ctx.metadata.firstUsefulOutputAt - requestStartTime),
6010
- }
6011
- : {}),
6026
+ ...claudeOutputLogFields(ctx.metadata, requestStartTime),
6012
6027
  ...(errorType ? { errorType } : {}),
6013
6028
  ...(errorMessage ? { errorMessage } : {}),
6014
6029
  ...(extra?.errorCode ? { errorCode: extra.errorCode } : {}),
@@ -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",