@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.
@@ -50,20 +50,18 @@ export function extractSSEEvents(buffer) {
50
50
  const rawBlock = buffer.slice(cursor, boundary);
51
51
  cursor = boundary + boundaryMatch[0].length;
52
52
  let eventType = "";
53
- let dataValue = "";
53
+ const dataLines = [];
54
54
  const lines = rawBlock.split(/\r\n|\n|\r/);
55
55
  for (const line of lines) {
56
- if (line.startsWith("event: ")) {
57
- eventType = line.slice(7).trim();
58
- }
59
- else if (line.startsWith("data: ")) {
60
- dataValue = line.slice(6);
56
+ if (line.startsWith("event:")) {
57
+ eventType = line.slice(6).trim();
61
58
  }
62
59
  else if (line.startsWith("data:")) {
63
- // handle `data:` with no space (edge case)
64
- dataValue = line.slice(5);
60
+ const value = line.slice(5);
61
+ dataLines.push(value.startsWith(" ") ? value.slice(1) : value);
65
62
  }
66
63
  }
64
+ const dataValue = dataLines.join("\n");
67
65
  if (eventType || dataValue) {
68
66
  events.push({ event: eventType, data: dataValue });
69
67
  }
@@ -75,6 +73,7 @@ export function extractSSEEvents(buffer) {
75
73
  // ---------------------------------------------------------------------------
76
74
  function createAccumulator(captureRawText) {
77
75
  return {
76
+ messageStopReceived: false,
78
77
  messageId: "",
79
78
  model: "",
80
79
  inputTokens: 0,
@@ -171,6 +170,8 @@ function finalize(acc) {
171
170
  const totalTokens = acc.inputTokens + acc.outputTokens;
172
171
  return {
173
172
  messageId: acc.messageId,
173
+ messageStopReceived: acc.messageStopReceived,
174
+ firstUsefulOutputAt: acc.firstUsefulOutputAt,
174
175
  model: acc.model,
175
176
  usage: {
176
177
  inputTokens: acc.inputTokens,
@@ -327,7 +328,11 @@ function processEvent(acc, event) {
327
328
  // Malformed JSON — skip silently, bytes already forwarded to client
328
329
  return;
329
330
  }
330
- if (event.event === "error") {
331
+ const payloadType = parsed && typeof parsed === "object" && "type" in parsed
332
+ ? parsed.type
333
+ : undefined;
334
+ const eventType = event.event || (typeof payloadType === "string" ? payloadType : "");
335
+ if (eventType === "error" || payloadType === "error") {
331
336
  const payload = parsed && typeof parsed === "object"
332
337
  ? parsed
333
338
  : {};
@@ -340,7 +345,26 @@ function processEvent(acc, event) {
340
345
  ? truncateString(message.trim(), MAX_EVENT_DATA_BYTES)
341
346
  : truncateString(event.data, MAX_EVENT_DATA_BYTES);
342
347
  }
343
- switch (event.event) {
348
+ if (eventType === "message_stop") {
349
+ acc.messageStopReceived = true;
350
+ }
351
+ if (eventType === "content_block_delta" &&
352
+ parsed &&
353
+ typeof parsed === "object" &&
354
+ "delta" in parsed) {
355
+ const delta = parsed.delta;
356
+ if (delta &&
357
+ typeof delta === "object" &&
358
+ (("text" in delta &&
359
+ typeof delta.text === "string" &&
360
+ delta.text.length > 0) ||
361
+ ("partial_json" in delta &&
362
+ typeof delta.partial_json === "string" &&
363
+ delta.partial_json.length > 0))) {
364
+ acc.firstUsefulOutputAt ??= Date.now();
365
+ }
366
+ }
367
+ switch (eventType) {
344
368
  case "message_start":
345
369
  processMessageStart(acc, parsed);
346
370
  break;
@@ -415,14 +439,8 @@ export function createSSEInterceptor(options = {}) {
415
439
  appendRawTextChunk(acc, finalChunk);
416
440
  sseBuffer += finalChunk;
417
441
  }
418
- // Process any trailing data left in the buffer (e.g. a final event
419
- // not followed by a double-newline).
420
- if (sseBuffer.trim()) {
421
- const { events } = extractSSEEvents(sseBuffer + "\n\n");
422
- for (const event of events) {
423
- processEvent(acc, event);
424
- }
425
- }
442
+ // SSE dispatch requires a blank line. An unterminated terminal event
443
+ // is not evidence that the client could observe protocol completion.
426
444
  settle();
427
445
  },
428
446
  });
@@ -2,4 +2,4 @@ import type { AnthropicStreamPreflightResult, StreamTerminalOutcome, StreamTermi
2
2
  /** Hold only pre-commit SSE frames so immediate upstream errors remain retryable. */
3
3
  export declare function preflightAnthropicStream(reader: ReadableStreamDefaultReader<Uint8Array>): Promise<AnthropicStreamPreflightResult>;
4
4
  export declare function createStreamTerminalOutcomeTracker(): StreamTerminalOutcomeTracker;
5
- export declare function mergeStreamTerminalOutcome(outcome: StreamTerminalOutcome, sseErrorMessage?: string): StreamTerminalOutcome;
5
+ export declare function mergeStreamTerminalOutcome(outcome: StreamTerminalOutcome, sseErrorMessage?: string, messageStopReceived?: boolean): StreamTerminalOutcome;
@@ -95,9 +95,15 @@ export function createStreamTerminalOutcomeTracker() {
95
95
  cancel: () => settle({ kind: "client_cancelled" }),
96
96
  };
97
97
  }
98
- export function mergeStreamTerminalOutcome(outcome, sseErrorMessage) {
98
+ export function mergeStreamTerminalOutcome(outcome, sseErrorMessage, messageStopReceived) {
99
99
  if (outcome.kind === "completed" && sseErrorMessage) {
100
100
  return { kind: "upstream_error", message: sseErrorMessage };
101
101
  }
102
+ if (outcome.kind === "completed" && messageStopReceived === false) {
103
+ return {
104
+ kind: "upstream_error",
105
+ message: "Anthropic stream ended without a message_stop event",
106
+ };
107
+ }
102
108
  return outcome;
103
109
  }
@@ -26,7 +26,7 @@ import { MAX_COOLDOWN_MS_BY_REASON } from "../../proxy/routingEvidence.js";
26
26
  import { buildProxyLimitHeaders, summarizePoolHeadroom, } from "../../proxy/quotaHeaders.js";
27
27
  import { buildClaudeError, ClaudeStreamSerializer, generateToolUseId, parseClaudeRequest, serializeClaudeResponse, } from "../../proxy/claudeFormat.js";
28
28
  import { CodexFallbackResponseError, consumeCodexFallbackResponse, createCodexFallbackStream, convertClaudeRequestToCodex, } from "../../proxy/codexFallback.js";
29
- import { registerProxyResponseObserver } from "../../proxy/proxyActivity.js";
29
+ import { registerProxyResponseObserver, takeProxyResponseObservers, trackProxyResponse, } from "../../proxy/proxyActivity.js";
30
30
  import { buildAnthropicModelsListResponse, buildTranslationOptions, extractText, extractToolArgs, extractUsageFromStreamResult, handleTranslatedJsonRequest, handleTranslatedStreamRequest, hasTranslatedOutput, } from "../../proxy/proxyTranslationEngine.js";
31
31
  import { tracers } from "../../telemetry/tracers.js";
32
32
  import { withSpan } from "../../telemetry/withSpan.js";
@@ -2408,6 +2408,7 @@ async function handleClaudePassthroughStreamResponse(args) {
2408
2408
  const trackedStream = trackUpstreamReadableStream(responseBody);
2409
2409
  let streamSource = trackedStream.stream;
2410
2410
  let streamFinalized = false;
2411
+ let telemetryDone;
2411
2412
  const finalizeStream = (status, errorType, errorMessage, usage) => {
2412
2413
  if (streamFinalized) {
2413
2414
  return false;
@@ -2429,9 +2430,14 @@ async function handleClaudePassthroughStreamResponse(args) {
2429
2430
  const capturedUpstreamSpan = upstreamSpan;
2430
2431
  const capturedResponse = response;
2431
2432
  const capturedRequestBytes = bodyStr.length;
2432
- Promise.all([telemetry, clientCapture, trackedStream.outcome])
2433
+ telemetryDone = Promise.all([
2434
+ telemetry,
2435
+ clientCapture,
2436
+ trackedStream.outcome,
2437
+ ])
2433
2438
  .then(([data, clientBody, rawOutcome]) => {
2434
- const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage);
2439
+ const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage, data.messageStopReceived);
2440
+ ctx.metadata.firstUsefulOutputAt = data.firstUsefulOutputAt;
2435
2441
  const failure = getStreamFailureDetails(terminalOutcome);
2436
2442
  capturedTracer.setUsage({
2437
2443
  inputTokens: data.usage.inputTokens,
@@ -2509,7 +2515,7 @@ async function handleClaudePassthroughStreamResponse(args) {
2509
2515
  });
2510
2516
  }
2511
2517
  catch {
2512
- trackedStream.outcome.then((outcome) => {
2518
+ telemetryDone = trackedStream.outcome.then((outcome) => {
2513
2519
  const failure = getStreamFailureDetails(outcome);
2514
2520
  upstreamSpan?.end();
2515
2521
  if (failure) {
@@ -2526,9 +2532,14 @@ async function handleClaudePassthroughStreamResponse(args) {
2526
2532
  captureRawText: true,
2527
2533
  });
2528
2534
  streamSource = streamSource.pipeThrough(interceptor);
2529
- Promise.all([telemetry, clientCapture, trackedStream.outcome])
2535
+ telemetryDone = Promise.all([
2536
+ telemetry,
2537
+ clientCapture,
2538
+ trackedStream.outcome,
2539
+ ])
2530
2540
  .then(([data, clientBody, rawOutcome]) => {
2531
- const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage);
2541
+ const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage, data.messageStopReceived);
2542
+ ctx.metadata.firstUsefulOutputAt = data.firstUsefulOutputAt;
2532
2543
  const failure = getStreamFailureDetails(terminalOutcome);
2533
2544
  finalizeStream(failure?.status ?? response.status, failure?.errorType, failure?.message, {
2534
2545
  inputTokens: data.usage.inputTokens,
@@ -2570,13 +2581,16 @@ async function handleClaudePassthroughStreamResponse(args) {
2570
2581
  catch {
2571
2582
  // Streaming capture is best-effort; the tracked source still propagates
2572
2583
  // the transport failure to the client.
2573
- trackedStream.outcome.then((outcome) => {
2584
+ telemetryDone = trackedStream.outcome.then((outcome) => {
2574
2585
  const failure = getStreamFailureDetails(outcome);
2575
2586
  finalizeStream(failure?.status ?? response.status, failure?.errorType, failure?.message);
2576
2587
  });
2577
2588
  }
2578
2589
  }
2579
2590
  const clientStream = streamSource.pipeThrough(clientCaptureStream);
2591
+ registerProxyResponseObserver(ctx.metadata, {
2592
+ onTerminal: () => telemetryDone,
2593
+ });
2580
2594
  return new Response(clientStream, {
2581
2595
  status: response.status,
2582
2596
  // Upstream headers first, then the proxy's own — passthrough already
@@ -3655,7 +3669,21 @@ async function executeClaudeCodexFallback(args) {
3655
3669
  // validation. A failed Codex attempt must not look like a served request.
3656
3670
  responseHeaders: {},
3657
3671
  };
3658
- const codexResponse = await handleCodexResponsesRequest(codexCtx);
3672
+ const childResponse = await handleCodexResponsesRequest(codexCtx);
3673
+ const childObservers = takeProxyResponseObservers(codexCtx.metadata);
3674
+ let finishChildAccounting = () => { };
3675
+ const childAccounting = new Promise((resolve) => {
3676
+ finishChildAccounting = resolve;
3677
+ });
3678
+ // The translated child has no HTTP runtime of its own. Drive its terminal
3679
+ // observers here so stream failures enrich the actual Codex attempt, while
3680
+ // the parent remains the sole client request in final success/error totals.
3681
+ const codexResponse = trackProxyResponse(childResponse, finishChildAccounting, {
3682
+ onTerminal: (details) => Promise.allSettled(childObservers.map(async (observer) => observer.onTerminal?.(details))),
3683
+ });
3684
+ registerProxyResponseObserver(ctx.metadata, {
3685
+ onTerminal: () => childAccounting,
3686
+ });
3659
3687
  const codexHeaders = { ...(codexCtx.responseHeaders ?? {}) };
3660
3688
  if (body.stream) {
3661
3689
  const bridge = await createCodexFallbackStream(codexResponse, body.model);
@@ -3753,6 +3781,10 @@ async function executeClaudeCodexFallback(args) {
3753
3781
  return;
3754
3782
  }
3755
3783
  const frame = capture(next.value);
3784
+ if (frame.startsWith("event: content_block_delta\n") &&
3785
+ /"(?:text|partial_json)":"(?:[^"\\]|\\.)+"/.test(frame)) {
3786
+ ctx.metadata.firstUsefulOutputAt ??= Date.now();
3787
+ }
3756
3788
  if (frame.startsWith("event: message_stop\n")) {
3757
3789
  // Finalize before exposing the terminal frame: a client can close
3758
3790
  // immediately after receiving it without making another pull.
@@ -3911,6 +3943,13 @@ async function tryConfiguredClaudeFallbackChain(args) {
3911
3943
  const { ctx, body, parsedFallbackRequest, fallbackPlan: providedFallbackPlan, modelRouter, tracer, requestStartTime, logProxyBody, logFinalRequest, } = args;
3912
3944
  const fallbackPlan = providedFallbackPlan ??
3913
3945
  buildProxyTranslationPlan({ provider: "anthropic", model: body.model }, modelRouter?.getFallbackChain() ?? [], body.model, parsedFallbackRequest);
3946
+ ctx.metadata.fallbackPlan = fallbackPlan.attempts
3947
+ .slice(1)
3948
+ .map(({ provider, model, reasoningEffort }) => ({
3949
+ provider,
3950
+ model,
3951
+ ...(reasoningEffort ? { reasoningEffort } : {}),
3952
+ }));
3914
3953
  logProxyBody({
3915
3954
  phase: "routing_decision",
3916
3955
  contentType: "application/json",
@@ -4669,6 +4708,7 @@ async function handleAnthropicStreamingSuccessResponse(args) {
4669
4708
  attemptNumber,
4670
4709
  finalBodyStr,
4671
4710
  upstreamSpan,
4711
+ logAttempt,
4672
4712
  logProxyBody,
4673
4713
  logFinalRequest,
4674
4714
  });
@@ -4692,13 +4732,14 @@ function getStreamFailureDetails(outcome) {
4692
4732
  }
4693
4733
  return undefined;
4694
4734
  }
4695
- function recordCommittedAnthropicStreamAttemptFailure(outcome, account) {
4735
+ function recordCommittedAnthropicStreamAttemptFailure(outcome, account, logAttempt) {
4696
4736
  if (outcome.kind === "upstream_error") {
4697
4737
  recordAttemptError(account.label, account.type, 502);
4738
+ logAttempt(502, "stream_error", outcome.message, { retryable: false });
4698
4739
  }
4699
4740
  }
4700
4741
  function attachAnthropicSuccessStreamTelemetry(args) {
4701
- const { ctx, account, response, responseHeaders, remainingStream, streamOutcome, tracer, requestStartTime, attemptNumber, finalBodyStr, upstreamSpan, logProxyBody, logFinalRequest, } = args;
4742
+ const { ctx, account, response, responseHeaders, remainingStream, streamOutcome, tracer, requestStartTime, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, logFinalRequest, } = args;
4702
4743
  const { stream: clientCaptureStream, capture: clientCapture } = createRawStreamCapture();
4703
4744
  let streamSource = remainingStream;
4704
4745
  let telemetryDone;
@@ -4716,8 +4757,9 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4716
4757
  const capturedAccountKey = account.key;
4717
4758
  telemetryDone = Promise.all([telemetry, clientCapture, streamOutcome])
4718
4759
  .then(([data, clientBody, rawOutcome]) => {
4719
- const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage);
4720
- recordCommittedAnthropicStreamAttemptFailure(terminalOutcome, account);
4760
+ const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage, data.messageStopReceived);
4761
+ ctx.metadata.firstUsefulOutputAt = data.firstUsefulOutputAt;
4762
+ recordCommittedAnthropicStreamAttemptFailure(terminalOutcome, account, logAttempt);
4721
4763
  capturedTracer.setUsage({
4722
4764
  inputTokens: data.usage.inputTokens,
4723
4765
  outputTokens: data.usage.outputTokens,
@@ -4814,7 +4856,7 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4814
4856
  // still settle the request from the actual stream terminal outcome.
4815
4857
  telemetryDone = streamOutcome
4816
4858
  .then((outcome) => {
4817
- recordCommittedAnthropicStreamAttemptFailure(outcome, account);
4859
+ recordCommittedAnthropicStreamAttemptFailure(outcome, account, logAttempt);
4818
4860
  const failure = getStreamFailureDetails(outcome);
4819
4861
  upstreamSpan?.end();
4820
4862
  if (failure) {
@@ -4845,8 +4887,9 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4845
4887
  streamOutcome,
4846
4888
  ])
4847
4889
  .then(([data, clientBody, rawOutcome]) => {
4848
- const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage);
4849
- recordCommittedAnthropicStreamAttemptFailure(terminalOutcome, account);
4890
+ const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage, data.messageStopReceived);
4891
+ ctx.metadata.firstUsefulOutputAt = data.firstUsefulOutputAt;
4892
+ recordCommittedAnthropicStreamAttemptFailure(terminalOutcome, account, logAttempt);
4850
4893
  const failure = getStreamFailureDetails(terminalOutcome);
4851
4894
  const usage = {
4852
4895
  inputTokens: data.usage.inputTokens,
@@ -4915,7 +4958,7 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4915
4958
  });
4916
4959
  telemetryDone = streamOutcome
4917
4960
  .then((outcome) => {
4918
- recordCommittedAnthropicStreamAttemptFailure(outcome, account);
4961
+ recordCommittedAnthropicStreamAttemptFailure(outcome, account, logAttempt);
4919
4962
  const failure = getStreamFailureDetails(outcome);
4920
4963
  if (failure) {
4921
4964
  logFinalRequest(failure.status, account.label, account.type, failure.errorType, failure.message);
@@ -4928,6 +4971,9 @@ function attachAnthropicSuccessStreamTelemetry(args) {
4928
4971
  }
4929
4972
  }
4930
4973
  const clientStream = streamSource.pipeThrough(clientCaptureStream);
4974
+ registerProxyResponseObserver(ctx.metadata, {
4975
+ onTerminal: () => telemetryDone,
4976
+ });
4931
4977
  // Limit headers published on the context are applied here rather than left
4932
4978
  // to the runtime wrapper: this Response goes straight to the client on every
4933
4979
  // mount (proxy runtime and the generic server adapters alike), so the
@@ -5958,6 +6004,11 @@ function createClaudeRequestRuntimeContext(args) {
5958
6004
  ...buildClientAttribution(ctx.headers),
5959
6005
  responseStatus: status,
5960
6006
  responseTimeMs: Date.now() - requestStartTime,
6007
+ ...(typeof ctx.metadata.firstUsefulOutputAt === "number"
6008
+ ? {
6009
+ firstUsefulOutputMs: Math.max(0, ctx.metadata.firstUsefulOutputAt - requestStartTime),
6010
+ }
6011
+ : {}),
5961
6012
  ...(errorType ? { errorType } : {}),
5962
6013
  ...(errorMessage ? { errorMessage } : {}),
5963
6014
  ...(extra?.errorCode ? { errorCode: extra.errorCode } : {}),
@@ -5980,6 +6031,9 @@ function createClaudeRequestRuntimeContext(args) {
5980
6031
  ? { traceId: traceCtx.traceId, spanId: traceCtx.spanId }
5981
6032
  : {}),
5982
6033
  ...(routingDecision ? { routingDecision } : {}),
6034
+ ...(Array.isArray(ctx.metadata.fallbackPlan)
6035
+ ? { fallbackPlan: ctx.metadata.fallbackPlan }
6036
+ : {}),
5983
6037
  });
5984
6038
  };
5985
6039
  const buildLoggedClaudeError = (status, message, errorType, extra) => {
@@ -281,6 +281,13 @@ export async function handleCodexResponsesRequest(ctx) {
281
281
  // not an independently final client request. The parent fallback owns the
282
282
  // final status and can still recover with a later provider.
283
283
  const isFallbackRequest = ctx.metadata?.[CODEX_FALLBACK_METADATA_KEY] === true;
284
+ const reasoning = body.reasoning;
285
+ const reasoningEffort = reasoning &&
286
+ typeof reasoning === "object" &&
287
+ "effort" in reasoning &&
288
+ typeof reasoning.effort === "string"
289
+ ? reasoning.effort
290
+ : undefined;
284
291
  const writeFinalLog = (account, responseStatus, extra = {}) => logRequest({
285
292
  timestamp: new Date().toISOString(),
286
293
  requestId: ctx.requestId,
@@ -328,6 +335,10 @@ export async function handleCodexResponsesRequest(ctx) {
328
335
  timestamp: new Date().toISOString(),
329
336
  requestId: ctx.requestId,
330
337
  attempt,
338
+ ...(isFallbackRequest
339
+ ? { parentRequestId: ctx.requestId.replace(/:codex-fallback$/, "") }
340
+ : {}),
341
+ ...(reasoningEffort ? { reasoningEffort } : {}),
331
342
  method: ctx.method,
332
343
  path: ctx.path,
333
344
  model,
@@ -473,23 +484,37 @@ export async function handleCodexResponsesRequest(ctx) {
473
484
  ...(ctx.responseHeaders ?? {}),
474
485
  };
475
486
  if (!upstream.body) {
476
- await recordFinalOutcome(account, upstream.status, {
477
- terminalOutcome: "bodyless",
487
+ recordAttemptError(account.label, CODEX_ACCOUNT_TYPE, 502);
488
+ writeAttempt(account, attempt, attemptStartedAt, 502, {
489
+ errorType: "incomplete_stream",
490
+ errorMessage: "Codex returned no response stream",
491
+ retryable: false,
492
+ });
493
+ await recordFinalOutcome(account, 502, {
494
+ terminalOutcome: "stream_error",
495
+ errorType: "incomplete_stream",
496
+ errorMessage: "Codex returned no response stream",
478
497
  });
479
498
  return new Response(upstream.body, {
480
499
  status: upstream.status,
481
500
  headers,
482
501
  });
483
502
  }
484
- const { stream: usageTap, usage: usageSeen } = createCodexUsageTap();
503
+ const { stream: usageTap, usage: usageSeen, evidence, } = createCodexUsageTap();
485
504
  const relay = new Response(upstream.body.pipeThrough(usageTap), {
486
505
  status: upstream.status,
487
506
  headers,
488
507
  });
489
508
  registerProxyResponseObserver(ctx.metadata, {
490
- onTerminal: ({ outcome }) => {
491
- void usageSeen
509
+ onTerminal: ({ outcome, error, observedBodyBytes }) => {
510
+ return usageSeen
492
511
  .then((usage) => {
512
+ const semantic = evidence();
513
+ const completedFrameDelivered = semantic.completed &&
514
+ observedBodyBytes >= semantic.terminalBytes;
515
+ const failed = semantic.errorType ||
516
+ ((outcome === "completed" || outcome === "bodyless") &&
517
+ !semantic.completed);
493
518
  const usageExtra = usage
494
519
  ? {
495
520
  inputTokens: usage.inputTokens,
@@ -498,10 +523,46 @@ export async function handleCodexResponsesRequest(ctx) {
498
523
  cacheCreationTokens: usage.cacheCreationTokens,
499
524
  }
500
525
  : {};
501
- if (outcome === "completed" || outcome === "bodyless") {
526
+ const timing = semantic.firstUsefulOutputAt === undefined
527
+ ? {}
528
+ : {
529
+ firstUsefulOutputMs: Math.max(0, semantic.firstUsefulOutputAt - requestStartTime),
530
+ };
531
+ if (failed) {
532
+ recordAttemptError(account.label, CODEX_ACCOUNT_TYPE, 502);
533
+ writeAttempt(account, attempt, attemptStartedAt, 502, {
534
+ errorType: semantic.errorType ?? "incomplete_stream",
535
+ errorCode: semantic.errorCode,
536
+ errorMessage: semantic.errorMessage ??
537
+ "Codex stream ended without a completion event",
538
+ retryable: false,
539
+ });
540
+ return recordFinalOutcome(account, 502, {
541
+ ...usageExtra,
542
+ ...timing,
543
+ terminalOutcome: "stream_error",
544
+ errorType: semantic.errorType ?? "incomplete_stream",
545
+ errorCode: semantic.errorCode,
546
+ errorMessage: semantic.errorMessage ??
547
+ "Codex stream ended without a completion event",
548
+ });
549
+ }
550
+ if (outcome === "completed" ||
551
+ outcome === "bodyless" ||
552
+ (outcome === "client_cancelled" && completedFrameDelivered)) {
502
553
  return recordFinalOutcome(account, upstream.status, {
503
- terminalOutcome: outcome,
554
+ terminalOutcome: "completed",
504
555
  ...usageExtra,
556
+ ...timing,
557
+ });
558
+ }
559
+ if (outcome === "stream_error") {
560
+ recordAttemptError(account.label, CODEX_ACCOUNT_TYPE, 502);
561
+ writeAttempt(account, attempt, attemptStartedAt, 502, {
562
+ errorType: "stream_error",
563
+ errorCode: getCodexTransportErrorCode(error),
564
+ errorMessage: summarizeCodexUpstreamError(error instanceof Error ? error.message : "", "Codex upstream stream failed"),
565
+ retryable: false,
505
566
  });
506
567
  }
507
568
  return recordFinalOutcome(account, outcome === "client_cancelled" ? 499 : 502, {
@@ -510,9 +571,13 @@ export async function handleCodexResponsesRequest(ctx) {
510
571
  : "stream_error",
511
572
  errorMessage: outcome === "client_cancelled"
512
573
  ? "Client cancelled Codex stream"
513
- : "Codex upstream stream failed",
574
+ : summarizeCodexUpstreamError(error instanceof Error ? error.message : "", "Codex upstream stream failed"),
575
+ ...(outcome === "stream_error"
576
+ ? { errorCode: getCodexTransportErrorCode(error) }
577
+ : {}),
514
578
  terminalOutcome: outcome,
515
579
  ...usageExtra,
580
+ ...timing,
516
581
  });
517
582
  })
518
583
  .catch(() => undefined);
@@ -555,6 +555,14 @@ export type ProxyAccountSortMetrics = {
555
555
  scopedSaturated: boolean;
556
556
  };
557
557
  export type RequestLogEntry = {
558
+ /** First output text or tool-argument delta, excluding SSE control frames. */
559
+ firstUsefulOutputMs?: number;
560
+ /** Small routing evidence retained even when response bodies are pruned. */
561
+ fallbackPlan?: Array<{
562
+ provider: string;
563
+ model: string;
564
+ reasoningEffort?: string;
565
+ }>;
558
566
  timestamp: string;
559
567
  requestId: string;
560
568
  method: string;
@@ -605,9 +613,31 @@ export type RequestLogEntry = {
605
613
  /** Exact secret-free inputs and result of initial account selection. */
606
614
  routingDecision?: ProxyAccountRoutingDecision;
607
615
  };
616
+ /** File-sink evidence is independent of model/request success counters. */
617
+ export type ProxyRequestLogSinkSnapshot = {
618
+ attempted: number;
619
+ written: number;
620
+ inFlight: number;
621
+ pending: number;
622
+ /** Records not admitted because the bounded writer queue was full. */
623
+ dropped: number;
624
+ writeTimeouts: number;
625
+ unconfirmedWrites: number;
626
+ lastErrorCode?: string;
627
+ };
628
+ export type ProxyRequestLoggerSnapshot = {
629
+ enabled: boolean;
630
+ requests: ProxyRequestLogSinkSnapshot;
631
+ attempts: ProxyRequestLogSinkSnapshot;
632
+ debug: ProxyRequestLogSinkSnapshot;
633
+ };
608
634
  export type RequestAttemptLogEntry = {
609
635
  timestamp: string;
610
636
  requestId: string;
637
+ /** Parent client request for an internal fallback invocation. */
638
+ parentRequestId?: string;
639
+ /** Requested effort retained independently of full body captures. */
640
+ reasoningEffort?: string;
611
641
  attempt: number;
612
642
  method: string;
613
643
  path: string;
@@ -649,7 +679,7 @@ export type RequestAttemptLogEntry = {
649
679
  spanId?: string;
650
680
  };
651
681
  /** Additional fields recorded when a Codex response becomes client-final. */
652
- export type CodexFinalLogExtra = Partial<Pick<RequestLogEntry, "errorType" | "errorMessage" | "errorCode" | "transportScope" | "inputTokens" | "outputTokens" | "cacheReadTokens" | "cacheCreationTokens" | "terminalOutcome">>;
682
+ export type CodexFinalLogExtra = Partial<Pick<RequestLogEntry, "errorType" | "errorMessage" | "errorCode" | "transportScope" | "inputTokens" | "outputTokens" | "cacheReadTokens" | "cacheCreationTokens" | "terminalOutcome" | "firstUsefulOutputMs">>;
653
683
  /** Additional fields recorded for each upstream Codex account attempt. */
654
684
  export type CodexAttemptLogExtra = Partial<Pick<RequestAttemptLogEntry, "errorType" | "errorMessage" | "errorCode" | "transportScope" | "retryable" | "rateLimitKind" | "cooldownReason">>;
655
685
  /** Minimal persistence contract needed by Codex rotating-token refreshes. */
@@ -1580,15 +1610,17 @@ export type ProxyResponseTrackingObserver = {
1580
1610
  }) => void;
1581
1611
  onTerminal?: (details: {
1582
1612
  outcome: ProxyResponseTerminalOutcome;
1613
+ /** Underlying read failure for structured transport diagnostics. */
1614
+ error?: unknown;
1583
1615
  /** Decoded response-body bytes observed by the adapter. */
1584
1616
  observedBodyBytes: number;
1585
1617
  responseChunks: number;
1586
- }) => void;
1618
+ }) => unknown;
1587
1619
  };
1588
1620
  /** Versioned lifecycle event names persisted by the proxy adapter. */
1589
1621
  export type ProxyLifecycleEventName = "request_accepted" | "response_headers" | "response_first_chunk" | "request_terminal";
1590
1622
  /** Client-facing terminal classifications recorded by lifecycle metadata. */
1591
- export type ProxyLifecycleTerminalOutcome = ProxyResponseTerminalOutcome | "handler_error";
1623
+ export type ProxyLifecycleTerminalOutcome = ProxyResponseTerminalOutcome | "handler_error" | "unknown";
1592
1624
  /** Content-free lifecycle event accepted by the bounded metadata logger. */
1593
1625
  export type ProxyLifecycleEventInput = {
1594
1626
  event: ProxyLifecycleEventName;
@@ -1601,6 +1633,13 @@ export type ProxyLifecycleEventInput = {
1601
1633
  sessionHash?: string;
1602
1634
  requestBytes?: number;
1603
1635
  responseStatus?: number;
1636
+ /** Semantic final status; the HTTP status may already have been committed. */
1637
+ finalStatus?: number;
1638
+ /** Terminal bookkeeping health, separate from the model outcome. */
1639
+ telemetryStatus?: "complete" | "timeout" | "observer_error" | "missing_final";
1640
+ /** Transport completion is independent of successful model completion. */
1641
+ transportOutcome?: ProxyResponseTerminalOutcome;
1642
+ outcomeSource?: "final_request" | "transport_error" | "http_status" | "unknown";
1604
1643
  /** Decoded response-body bytes observed by the adapter. */
1605
1644
  observedBodyBytes?: number;
1606
1645
  responseChunks?: number;
@@ -1627,6 +1666,10 @@ export type ProxyLifecycleLoggerSnapshot = {
1627
1666
  writeFailures: number;
1628
1667
  /** Events requeued after a transient lifecycle metadata write failure. */
1629
1668
  writeRetries: number;
1669
+ /** Slow appends still owned by the original writer, never replayed on timeout. */
1670
+ writeTimeouts: number;
1671
+ /** Records in failed appends that may have partially reached the file. */
1672
+ unconfirmedWrites: number;
1630
1673
  pending: number;
1631
1674
  inFlight: number;
1632
1675
  flushing: boolean;
@@ -1698,6 +1741,12 @@ export type ProxyAnalysisReport = {
1698
1741
  unsupportedLifecycleLines: number;
1699
1742
  lifecycleSequenceGaps: number;
1700
1743
  lifecycleSequenceDuplicates: number;
1744
+ conflictingLifecycleDuplicates: number;
1745
+ duplicateAttempts: number;
1746
+ finalOutcomeConflicts: number;
1747
+ /** Missing evidence; may include in-flight or interrupted requests. */
1748
+ acceptedWithoutFinal: number;
1749
+ terminalWithoutFinal: number;
1701
1750
  streams: Record<ProxyAnalysisStreamName, {
1702
1751
  observedFrom: string | null;
1703
1752
  observedTo: string | null;
@@ -1755,6 +1804,7 @@ export type ProxyAnalysisReport = {
1755
1804
  latencyMs: {
1756
1805
  headers: ProxyLatencySummary;
1757
1806
  firstChunk: ProxyLatencySummary;
1807
+ firstUsefulOutput: ProxyLatencySummary;
1758
1808
  terminal: ProxyLatencySummary;
1759
1809
  finalRequest: ProxyLatencySummary;
1760
1810
  attempt: ProxyLatencySummary;
@@ -1817,6 +1867,7 @@ export type ProxyAnalysisAttemptRecord = {
1817
1867
  };
1818
1868
  /** Final request fields retained while joining offline proxy log records. */
1819
1869
  export type ProxyAnalysisFinalRequestRecord = {
1870
+ firstUsefulOutputMs: number | null;
1820
1871
  timestamp: string;
1821
1872
  status: number;
1822
1873
  durationMs: number | null;
@@ -1861,6 +1912,15 @@ export type CodexStreamUsage = {
1861
1912
  cacheCreationTokens: number;
1862
1913
  reasoningTokens: number;
1863
1914
  };
1915
+ /** Semantic completion evidence observed in native Codex SSE bytes. */
1916
+ export type CodexStreamEvidence = {
1917
+ completed: boolean;
1918
+ terminalBytes: number;
1919
+ firstUsefulOutputAt?: number;
1920
+ errorType?: string;
1921
+ errorMessage?: string;
1922
+ errorCode?: string;
1923
+ };
1864
1924
  /** Validated account-routing evidence joined to a final request log. */
1865
1925
  export type ProxyAnalysisRoutingRecord = {
1866
1926
  requestId: string;
@@ -1883,6 +1943,8 @@ export type RuntimeRequestMetadata = {
1883
1943
  rejectForUpdate?: boolean;
1884
1944
  terminalErrorType?: string;
1885
1945
  terminalErrorCode?: string;
1946
+ /** Canonical final record, populated synchronously before asynchronous I/O. */
1947
+ terminalResult?: RequestLogEntry;
1886
1948
  /** Releases this request's peer-share concurrency slot. Set by the share
1887
1949
  * gate for borrowed traffic; invoked once the response body completes, so a
1888
1950
  * long stream holds its slot for as long as it is actually streaming. */
@@ -2073,6 +2135,8 @@ export type SSEContentBlock = {
2073
2135
  };
2074
2136
  /** Aggregated telemetry resolved when an SSE stream completes. */
2075
2137
  export type SSETelemetry = {
2138
+ messageStopReceived: boolean;
2139
+ firstUsefulOutputAt?: number;
2076
2140
  messageId: string;
2077
2141
  model: string;
2078
2142
  usage: {
@@ -2115,6 +2179,8 @@ export type StreamTerminalOutcomeTracker = {
2115
2179
  };
2116
2180
  /** Mutable accumulator the SSE interceptor uses internally. */
2117
2181
  export type TelemetryAccumulator = {
2182
+ messageStopReceived: boolean;
2183
+ firstUsefulOutputAt?: number;
2118
2184
  messageId: string;
2119
2185
  model: string;
2120
2186
  inputTokens: number;