@bitkyc08/opencodex 2.72.0 → 2.73.0

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.
Files changed (79) hide show
  1. package/gui/dist/assets/{App-Cf-WYstE.js → App-mHGa7dE1.js} +15 -15
  2. package/gui/dist/assets/{Tray-DkM0mULy.js → Tray-BxY_RYZg.js} +1 -1
  3. package/gui/dist/assets/{index-PWrwOfio.js → index-ur_BPgK1.js} +12 -12
  4. package/gui/dist/assets/{quota-summary-D-FKtrgr.js → quota-summary-D1Vq7N9H.js} +1 -1
  5. package/gui/dist/index.html +1 -1
  6. package/package.json +1 -1
  7. package/src/adapters/cursor/native-exec.ts +28 -10
  8. package/src/adapters/devin/live-models.ts +5 -0
  9. package/src/adapters/kiro/reasoning.ts +2 -0
  10. package/src/adapters/openai-chat/messages.ts +4 -1
  11. package/src/adapters/openai-responses/passthrough.ts +12 -1
  12. package/src/adapters/openai-responses/reasoning.ts +15 -0
  13. package/src/bridge/internal.ts +12 -0
  14. package/src/cli/account-anthropic-threshold.ts +41 -0
  15. package/src/cli/account-api.ts +2 -0
  16. package/src/cli/account-extended.ts +32 -21
  17. package/src/cli/account.ts +1 -0
  18. package/src/cli/capabilities.ts +8 -4
  19. package/src/cli/index.ts +1 -1
  20. package/src/codex/catalog/build-entries.ts +6 -5
  21. package/src/codex/catalog/derive-entry.ts +3 -4
  22. package/src/codex/catalog/gather-capture.ts +2 -0
  23. package/src/codex/catalog/metadata.ts +6 -0
  24. package/src/codex/catalog/native-models.ts +17 -3
  25. package/src/codex/catalog/parsing.ts +4 -1
  26. package/src/codex/catalog/pinned-models.ts +3 -1
  27. package/src/codex/catalog/provider-models.ts +22 -2
  28. package/src/codex/data/roster-pinned-models.json +178 -1
  29. package/src/codex/model-entitlements.ts +1 -1
  30. package/src/codex/native-profile-store.ts +2 -1
  31. package/src/combos/resolve.ts +16 -1
  32. package/src/config/subagent-models.ts +30 -5
  33. package/src/generated/compatibility-version.json +95 -67
  34. package/src/generated/model-metadata.ts +4 -4
  35. package/src/lib/account-selection-events.ts +28 -0
  36. package/src/lib/errors.ts +7 -5
  37. package/src/lib/keyring-native.ts +132 -0
  38. package/src/lib/provider-outbound.ts +13 -0
  39. package/src/lib/retry-delay.ts +10 -0
  40. package/src/oauth/anthropic-account-threshold.ts +13 -0
  41. package/src/oauth/anthropic-routing.ts +223 -52
  42. package/src/oauth/index.ts +4 -2
  43. package/src/oauth/store.ts +45 -3
  44. package/src/oauth/types.ts +2 -0
  45. package/src/providers/api-key-resolve.ts +2 -4
  46. package/src/providers/codebuddy-models.ts +6 -0
  47. package/src/providers/kiro-models.ts +4 -0
  48. package/src/providers/label.ts +20 -1
  49. package/src/providers/quota/account-cache.ts +2 -1
  50. package/src/providers/quota/anthropic-cooldown-recovery.ts +118 -0
  51. package/src/providers/quota/vendor-probes-oauth.ts +84 -57
  52. package/src/providers/quota.ts +45 -46
  53. package/src/providers/registry/entries-core.ts +3 -1
  54. package/src/providers/registry/entries-extended.ts +14 -1
  55. package/src/providers/registry/model-seeds.ts +7 -1
  56. package/src/server/claude-messages.ts +84 -27
  57. package/src/server/management/anthropic-account-threshold.ts +27 -0
  58. package/src/server/management/oauth-account-routes.ts +17 -4
  59. package/src/server/management/route-registry.ts +1 -0
  60. package/src/server/messages-native-oauth.ts +20 -21
  61. package/src/server/messages-native.ts +39 -14
  62. package/src/server/relay-eager.ts +5 -2
  63. package/src/server/relay.ts +268 -82
  64. package/src/server/responses/adapter-continuation.ts +6 -0
  65. package/src/server/responses/adapter-delivery.ts +25 -7
  66. package/src/server/responses/adapter-dispatch.ts +31 -0
  67. package/src/server/responses/buffered-sse-json.ts +345 -0
  68. package/src/server/responses/core-codex-account.ts +4 -1
  69. package/src/server/responses/core-combo.ts +28 -0
  70. package/src/server/responses/core-lifetime.ts +4 -0
  71. package/src/server/responses/passthrough-delivery.ts +305 -10
  72. package/src/server/responses/passthrough-dispatch.ts +60 -25
  73. package/src/server/responses/passthrough-error.ts +3 -1
  74. package/src/server/responses/request-transport.ts +22 -5
  75. package/src/server/responses/terminal-error-redaction.ts +53 -0
  76. package/src/service/windows-taskxml.ts +20 -6
  77. package/src/service-manager-probe.ts +115 -6
  78. package/src/types/wire.ts +7 -0
  79. package/src/usage/expected-prices.ts +11 -0
@@ -29,14 +29,18 @@ import {
29
29
  EMPTY_BYTES,
30
30
  joinSseFrameBytes,
31
31
  MAX_CLIENT_SSE_FRAME_BYTES,
32
+ SseFrameCountLimitError,
32
33
  } from "./sse-frame-buffer";
33
34
  import { replaceSseDataPayload, sseDataPayload } from "./sse-payload-rewrite";
34
35
  import { createBoundedResponseLogBody } from "./response-log-body";
35
36
  import { clientWireLogOf } from "./inference/client-wire";
36
37
  import { recordClientWireRequestLog } from "./inference/client-wire-log";
38
+ import { nativeResponseFingerprint } from "./responses/native-response-json";
39
+ import { nativeResponseOutput } from "./responses/native-response-output";
37
40
 
38
41
  const nativePassthroughSseResponses = new WeakSet<Response>();
39
42
  const eagerRelaySseResponses = new WeakSet<Response>();
43
+ const preinspectedJsonResponses = new WeakSet<Response>();
40
44
 
41
45
  export const MAX_INSPECTION_SSE_FRAME_BYTES = MAX_CLIENT_SSE_FRAME_BYTES;
42
46
  export const MAX_COMPLETED_OUTPUT_ITEMS = 256;
@@ -124,12 +128,12 @@ export function relayWithAbort(
124
128
  });
125
129
  }
126
130
 
127
- export function buildFailedTailPayload(err: unknown): string {
131
+ export function buildFailedTailPayload(err: unknown, maskCredential?: (text: string) => string): string {
128
132
  const translatorOverflow = isTranslatorBudgetExceededError(err);
129
- const message = (translatorOverflow
133
+ const diagnostic = redactSecretString((translatorOverflow
130
134
  ? "upstream translation buffer exceeded the safe limit"
131
- : `Upstream stream terminated unexpectedly: ${err instanceof Error ? err.message : String(err)}`)
132
- .slice(0, MAX_TAIL_ERROR_MESSAGE_CHARS);
135
+ : `Upstream stream terminated unexpectedly: ${err instanceof Error ? err.message : String(err)}`));
136
+ const message = (maskCredential?.(diagnostic) ?? diagnostic).slice(0, MAX_TAIL_ERROR_MESSAGE_CHARS);
133
137
  const failure = {
134
138
  type: "upstream_error",
135
139
  code: translatorOverflow ? "translation_buffer_limit" : "upstream_reset",
@@ -141,9 +145,9 @@ export function buildFailedTailPayload(err: unknown): string {
141
145
  });
142
146
  }
143
147
 
144
- function buildFailedTailPayloadOrFallback(err: unknown): string {
148
+ function buildFailedTailPayloadOrFallback(err: unknown, maskCredential?: (text: string) => string): string {
145
149
  try {
146
- return buildFailedTailPayload(err);
150
+ return buildFailedTailPayload(err, maskCredential);
147
151
  } catch {
148
152
  // Error.message and String(error) may execute hostile accessors. Preserve a
149
153
  // bounded protocol terminal even when diagnostic serialization is unsafe.
@@ -151,8 +155,8 @@ function buildFailedTailPayloadOrFallback(err: unknown): string {
151
155
  }
152
156
  }
153
157
 
154
- export function failedTailFrame(encoder: TextEncoder, err: unknown): Uint8Array {
155
- const payload = buildFailedTailPayloadOrFallback(err);
158
+ export function failedTailFrame(encoder: TextEncoder, err: unknown, maskCredential?: (text: string) => string): Uint8Array {
159
+ const payload = buildFailedTailPayloadOrFallback(err, maskCredential);
156
160
  return encoder.encode(`\n\nevent: response.failed\ndata: ${payload}\n\n${DONE_SSE_FRAME_TEXT}`);
157
161
  }
158
162
 
@@ -170,17 +174,19 @@ export function upstreamErrorTailFrame(
170
174
  encoder: TextEncoder,
171
175
  message: string,
172
176
  refusalCode?: string,
177
+ maskCredential?: (text: string) => string,
173
178
  ): Uint8Array {
174
179
  return encoder.encode(
175
- `event: response.failed\ndata: ${upstreamErrorFailedPayload(message, refusalCode)}\n\n`,
180
+ `event: response.failed\ndata: ${upstreamErrorFailedPayload(message, refusalCode, maskCredential)}\n\n`,
176
181
  );
177
182
  }
178
183
 
179
- function upstreamErrorFailedPayload(message: string, refusalCode?: string): string {
184
+ function upstreamErrorFailedPayload(message: string, refusalCode?: string, maskCredential?: (text: string) => string): string {
185
+ const diagnostic = redactSecretString(message);
180
186
  const error = {
181
187
  type: refusalCode === undefined ? "upstream_error" : "invalid_request_error",
182
- code: refusalCode ?? "upstream_server_error",
183
- message: redactSecretString(message).slice(0, MAX_TAIL_ERROR_MESSAGE_CHARS),
188
+ code: refusalCode === undefined ? "upstream_server_error" : (maskCredential?.(refusalCode) ?? refusalCode),
189
+ message: (maskCredential?.(diagnostic) ?? diagnostic).slice(0, MAX_TAIL_ERROR_MESSAGE_CHARS),
184
190
  };
185
191
  return JSON.stringify({
186
192
  type: "response.failed",
@@ -206,8 +212,9 @@ export function refusalFailedTailFrame(
206
212
  encoder: TextEncoder,
207
213
  message: string,
208
214
  refusalCode: string,
215
+ maskCredential?: (text: string) => string,
209
216
  ): Uint8Array {
210
- const payload = upstreamErrorFailedPayload(message, refusalCode);
217
+ const payload = upstreamErrorFailedPayload(message, refusalCode, maskCredential);
211
218
  return encoder.encode(
212
219
  `\n\nevent: response.failed\ndata: ${payload}\n\n${DONE_SSE_FRAME_TEXT}`,
213
220
  );
@@ -238,6 +245,8 @@ function boundedBareUpstreamErrorMessage(payload: unknown): string | undefined {
238
245
  function boundedBareUpstreamError(payload: unknown): {
239
246
  message: string;
240
247
  refusalCode: string | undefined;
248
+ errorType: string | undefined;
249
+ errorCode: string | undefined;
241
250
  } | undefined {
242
251
  const root = asJsonRecord(payload);
243
252
  if (!root || root.type !== "error") return undefined;
@@ -247,21 +256,30 @@ function boundedBareUpstreamError(payload: unknown): {
247
256
  // that supplied the message also supplies the verdict. Taking the FIRST code
248
257
  // rather than searching for a refusal is what stops a refusal nested below a
249
258
  // transient one from overruling it.
250
- const code = [
259
+ const candidates = [
251
260
  asJsonRecord(root.error),
252
261
  asJsonRecord(root.last_error),
253
262
  asJsonRecord(response?.error),
254
263
  asJsonRecord(response?.incomplete_details),
255
264
  root,
256
- ]
257
- .map(candidate => stringField(candidate, "code"))
258
- .find(candidate => candidate !== undefined);
265
+ ];
266
+ const code = candidates.map(candidate => stringField(candidate, "code"))
267
+ .find(candidate => candidate !== undefined)?.slice(0, 128);
268
+ // Root `type` is the SSE event discriminator (`"error"`), not an error class. Only nested
269
+ // error records can authoritatively name classes such as rate_limit_error or server_error.
270
+ const errorType = candidates.slice(0, -1).map(candidate => stringField(candidate, "type"))
271
+ .find(candidate => candidate !== undefined)?.slice(0, 128);
259
272
  const refusalCode = code !== undefined
260
273
  ? (isTerminalRefusalCode(code) ? code : undefined)
261
274
  : message === undefined ? undefined : safetyRefusalCodeFromMessage(message);
262
- if (message !== undefined) return { message, refusalCode };
275
+ if (message !== undefined) return { message, refusalCode, errorType, errorCode: code };
263
276
  if (refusalCode === undefined) return undefined;
264
- return { message: terminalRefusalFallbackMessage(refusalCode), refusalCode };
277
+ return {
278
+ message: terminalRefusalFallbackMessage(refusalCode),
279
+ refusalCode,
280
+ errorType,
281
+ errorCode: code,
282
+ };
265
283
  }
266
284
 
267
285
  export type SseTerminalOutputBoundary = {
@@ -271,9 +289,30 @@ export type SseTerminalOutputBoundary = {
271
289
  doneSeen(): boolean;
272
290
  upstreamError(): string | undefined;
273
291
  upstreamRefusalCode(): string | undefined;
292
+ upstreamErrorType(): string | undefined;
293
+ upstreamErrorCode(): string | undefined;
274
294
  dispose(): void;
275
295
  };
276
296
 
297
+ export class SseAggregateLimitError extends Error {
298
+ readonly maxBytes: number;
299
+
300
+ constructor(maxBytes: number) {
301
+ super(`upstream SSE transcript exceeded ${maxBytes} bytes`);
302
+ this.name = "SseAggregateLimitError";
303
+ this.maxBytes = maxBytes;
304
+ }
305
+ }
306
+
307
+ export type SseTerminalOutputBoundaryOptions = CodexSafetyBufferingFilterOptions & {
308
+ /** Optional raw bytes admitted before the first terminal; omitted keeps streaming behavior. */
309
+ maxInputBytes?: number;
310
+ /** Optional client-facing bytes emitted through the first terminal. */
311
+ maxOutputBytes?: number;
312
+ /** Optional aggregate frame count through the first terminal. */
313
+ maxFrames?: number;
314
+ };
315
+
277
316
  /**
278
317
  * Frame-aware client output boundary shared by both native Responses relays.
279
318
  * It buffers only the current incomplete SSE block under the same hard byte
@@ -282,9 +321,21 @@ export type SseTerminalOutputBoundary = {
282
321
  * a terminal arrives so clean EOF can synthesize one terminal and one sentinel.
283
322
  */
284
323
  export function createSseTerminalOutputBoundary(
285
- options?: CodexSafetyBufferingFilterOptions,
324
+ options?: SseTerminalOutputBoundaryOptions,
286
325
  ): SseTerminalOutputBoundary {
287
326
  const dropSafetyBuffering = options?.dropCodexSafetyBuffering === true;
327
+ const maxInputBytes = options?.maxInputBytes;
328
+ const maxOutputBytes = options?.maxOutputBytes;
329
+ const maxFrames = options?.maxFrames;
330
+ if (maxInputBytes !== undefined && (!Number.isSafeInteger(maxInputBytes) || maxInputBytes <= 0)) {
331
+ throw new RangeError("maxInputBytes must be a positive safe integer");
332
+ }
333
+ if (maxOutputBytes !== undefined && (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes <= 0)) {
334
+ throw new RangeError("maxOutputBytes must be a positive safe integer");
335
+ }
336
+ if (maxFrames !== undefined && (!Number.isSafeInteger(maxFrames) || maxFrames <= 0)) {
337
+ throw new RangeError("maxFrames must be a positive safe integer");
338
+ }
288
339
  const decoder = new TextDecoder();
289
340
  const encoder = new TextEncoder();
290
341
  const framer = new BoundedSseFrameBuffer(MAX_INSPECTION_SSE_FRAME_BYTES);
@@ -294,12 +345,30 @@ export function createSseTerminalOutputBoundary(
294
345
  let disposed = false;
295
346
  let upstreamError: string | undefined;
296
347
  let upstreamRefusalCode: string | undefined;
348
+ let upstreamErrorType: string | undefined;
349
+ let upstreamErrorCode: string | undefined;
350
+ let inputBytes = 0;
351
+ let outputBytes = 0;
352
+ let framesSeen = 0;
297
353
 
298
354
  const processFrames = (
299
355
  frames: ReturnType<BoundedSseFrameBuffer["feed"]>,
300
356
  ): Uint8Array => {
301
357
  if (disposed || terminal || frames.length === 0) return EMPTY_BYTES;
358
+ if (maxFrames !== undefined && frames.length > maxFrames - framesSeen) {
359
+ throw new SseFrameCountLimitError(maxFrames);
360
+ }
361
+ framesSeen += frames.length;
302
362
  const output: Uint8Array[] = [];
363
+ const appendOutput = (...parts: Uint8Array[]): void => {
364
+ for (const part of parts) {
365
+ if (maxOutputBytes !== undefined && part.byteLength > maxOutputBytes - outputBytes) {
366
+ throw new SseAggregateLimitError(maxOutputBytes);
367
+ }
368
+ outputBytes += part.byteLength;
369
+ output.push(part);
370
+ }
371
+ };
303
372
  let responsesTerminal = false;
304
373
  for (const frame of frames) {
305
374
  const payload = sseDataPayload(decoder.decode(frame.block));
@@ -311,6 +380,8 @@ export function createSseTerminalOutputBoundary(
311
380
  if (bare !== undefined) {
312
381
  upstreamError = bare.message;
313
382
  upstreamRefusalCode = bare.refusalCode;
383
+ upstreamErrorType = bare.errorType;
384
+ upstreamErrorCode = bare.errorCode;
314
385
  }
315
386
  const safetyBuffering = dropSafetyBuffering && parsed !== undefined
316
387
  ? codexSafetyBufferingBlockAction(parsed) : "keep";
@@ -331,7 +402,7 @@ export function createSseTerminalOutputBoundary(
331
402
  if (isDone) {
332
403
  done = true;
333
404
  if (responsesTerminal) {
334
- output.push(outboundBlock, frame.delimiter);
405
+ appendOutput(outboundBlock, frame.delimiter);
335
406
  } else if (!pendingDone) {
336
407
  // Do not expose a sentinel before a Responses terminal. If EOF
337
408
  // follows, the synthetic incomplete path owns the one sentinel;
@@ -342,11 +413,11 @@ export function createSseTerminalOutputBoundary(
342
413
  }
343
414
  // Preserve every frame through the first Responses terminal. Every
344
415
  // later non-DONE frame is dropped.
345
- if (!responsesTerminal) output.push(outboundBlock, frame.delimiter);
416
+ if (!responsesTerminal) appendOutput(outboundBlock, frame.delimiter);
346
417
  if (!responsesTerminal && payload && terminalStatusFromParsed(parsed)) {
347
418
  responsesTerminal = true;
348
419
  if (pendingDone) {
349
- output.push(pendingDone.block, pendingDone.delimiter);
420
+ appendOutput(pendingDone.block, pendingDone.delimiter);
350
421
  pendingDone = null;
351
422
  }
352
423
  }
@@ -361,7 +432,26 @@ export function createSseTerminalOutputBoundary(
361
432
  return {
362
433
  feed(chunk) {
363
434
  if (disposed || terminal) return EMPTY_BYTES;
364
- return processFrames(framer.feed(chunk));
435
+ if (maxInputBytes === undefined) return processFrames(framer.feed(chunk));
436
+ // A fetch implementation may hand us one multi-megabyte chunk containing thousands of
437
+ // complete frames. Slice BEFORE framing so neither the frame array nor the final join can be
438
+ // allocated past the explicit transcript budget. Once a valid terminal appears, trailing
439
+ // bytes in the same network chunk retain the ordinary terminal-authoritative behavior.
440
+ const slices: Uint8Array[] = [];
441
+ for (let offset = 0; offset < chunk.byteLength && !terminal;) {
442
+ const remaining = maxInputBytes - inputBytes;
443
+ if (remaining <= 0) throw new SseAggregateLimitError(maxInputBytes);
444
+ const length = Math.min(64 * 1024, remaining, chunk.byteLength - offset);
445
+ const slice = chunk.subarray(offset, offset + length);
446
+ inputBytes += slice.byteLength;
447
+ offset += length;
448
+ const processed = processFrames(framer.feed(slice));
449
+ if (processed.byteLength > 0) slices.push(processed);
450
+ if (!terminal && offset < chunk.byteLength && inputBytes >= maxInputBytes) {
451
+ throw new SseAggregateLimitError(maxInputBytes);
452
+ }
453
+ }
454
+ return joinSseFrameBytes(slices);
365
455
  },
366
456
  finish() {
367
457
  if (disposed || terminal) return EMPTY_BYTES;
@@ -379,6 +469,8 @@ export function createSseTerminalOutputBoundary(
379
469
  doneSeen: () => done,
380
470
  upstreamError: () => upstreamError,
381
471
  upstreamRefusalCode: () => upstreamRefusalCode,
472
+ upstreamErrorType: () => upstreamErrorType,
473
+ upstreamErrorCode: () => upstreamErrorCode,
382
474
  dispose() {
383
475
  if (disposed) return;
384
476
  disposed = true;
@@ -400,7 +492,7 @@ export function relaySseWithFailedTail(
400
492
  body: ReadableStream<Uint8Array>,
401
493
  upstream: AbortController,
402
494
  onClientGone?: (reason?: unknown) => void,
403
- opts?: { upstreamError?: string; terminalBoundary?: CodexSafetyBufferingFilterOptions },
495
+ opts?: { upstreamError?: string; terminalBoundary?: CodexSafetyBufferingFilterOptions; maskCredential?: (text: string) => string },
404
496
  ): ReadableStream<Uint8Array> {
405
497
  const reader = body.getReader();
406
498
  const encoder = new TextEncoder();
@@ -454,6 +546,7 @@ export function relaySseWithFailedTail(
454
546
  encoder,
455
547
  upstreamError,
456
548
  terminalBoundary.upstreamRefusalCode(),
549
+ opts?.maskCredential,
457
550
  ));
458
551
  controller.enqueue(doneFrame(encoder));
459
552
  }
@@ -486,8 +579,8 @@ export function relaySseWithFailedTail(
486
579
  const refusalCode = terminalBoundary.upstreamRefusalCode();
487
580
  const refusalMessage = terminalBoundary.upstreamError();
488
581
  controller.enqueue(refusalCode !== undefined && refusalMessage !== undefined
489
- ? refusalFailedTailFrame(encoder, refusalMessage, refusalCode)
490
- : failedTailFrame(encoder, err));
582
+ ? refusalFailedTailFrame(encoder, refusalMessage, refusalCode, opts?.maskCredential)
583
+ : failedTailFrame(encoder, err, opts?.maskCredential));
491
584
  }
492
585
  controller.close();
493
586
  } catch { /* client already torn down */ }
@@ -828,9 +921,15 @@ export function responseWithDeferredRequestLog(
828
921
  }
829
922
  if (!response.body || !contentType.includes("text/event-stream")) {
830
923
  if (response.body && (contentType.includes("application/json") || response.status >= 400)) {
924
+ // Some delivery paths already parsed the bounded body while constructing the client JSON.
925
+ // Retain EOF/cancel logging there, but do not materialize and parse the same near-limit
926
+ // response a second time merely for metadata the first pass already published.
927
+ const preinspectedJson = contentType.includes("application/json")
928
+ && preinspectedJsonResponses.has(response);
831
929
  const body = createBoundedResponseLogBody(response.body, {
832
930
  json: contentType.includes("application/json"),
833
- inspect: text => inspectResponseLogJson(logCtx, text),
931
+ ...(preinspectedJson ? { maxInspectionBytes: 0 } : {}),
932
+ inspect: preinspectedJson ? () => undefined : text => inspectResponseLogJson(logCtx, text),
834
933
  finalize: reason => {
835
934
  // Preserve wire status; request history follows the adjacent SSE
836
935
  // convention for a client cancellation or upstream read failure.
@@ -890,6 +989,16 @@ export function markNativePassthroughSseResponse(response: Response): Response {
890
989
  return response;
891
990
  }
892
991
 
992
+ /** Mark bounded JSON whose request-log metadata was already derived from its source events. */
993
+ export function markPreinspectedJsonResponse(response: Response): Response {
994
+ preinspectedJsonResponses.add(response);
995
+ return response;
996
+ }
997
+
998
+ export function isPreinspectedJsonResponse(response: Response): boolean {
999
+ return preinspectedJsonResponses.has(response);
1000
+ }
1001
+
893
1002
  export function isNativePassthroughSseResponse(response: Response): boolean {
894
1003
  return nativePassthroughSseResponses.has(response);
895
1004
  }
@@ -995,6 +1104,21 @@ export type SseInspectorHandlers = {
995
1104
  onTerminal?: (status: ResponsesTerminalStatus, httpStatusOverride?: number) => void;
996
1105
  logCtx?: RequestLogContext;
997
1106
  onCompletedResponse?: (response: { id?: unknown; output?: unknown; status?: unknown }) => void;
1107
+ /** A reconstructed snapshot for any valid terminal kind; unlike persistence, includes failures. */
1108
+ onTerminalResponse?: (
1109
+ status: ResponsesTerminalStatus,
1110
+ response: { id?: unknown; output?: unknown; status?: unknown },
1111
+ ) => void;
1112
+ /**
1113
+ * Opt-in reconstruction policy for a caller that must materialize the whole terminal response.
1114
+ * Persistence inspectors omit this and retain their smaller historical caps/merge semantics.
1115
+ */
1116
+ terminalReconstruction?: {
1117
+ maxItems: number;
1118
+ maxSourceBytes: number;
1119
+ requireCompleteOutputIndices?: boolean;
1120
+ mergeSparseTerminalOutput?: boolean;
1121
+ };
998
1122
  /**
999
1123
  * Every parsed SSE payload, delivered BEFORE any onCompletedResponse derived from that same
1000
1124
  * payload. A caller that must decide on the whole turn -- not just its terminal snapshot --
@@ -1008,8 +1132,10 @@ export type SseInspectorHandlers = {
1008
1132
  * An inspector that only hears about parsed events cannot tell "nothing has been emitted"
1009
1133
  * from "something was emitted that I could not read", and a replay decision needs that
1010
1134
  * difference: an unreadable payload is a payload the caller may already have seen.
1135
+ * The payload lets bounded collectors distinguish the valid `[DONE]` sentinel from malformed
1136
+ * JSON without adding a second SSE parser; existing observers may ignore the argument.
1011
1137
  */
1012
- onOpaquePayload?: () => void;
1138
+ onOpaquePayload?: (payload: string | null) => void;
1013
1139
  onFirstOutput?: () => void;
1014
1140
  /**
1015
1141
  * Provider-scoped compatibility: persist the completed snapshot under the
@@ -1021,6 +1147,13 @@ export type SseInspectorHandlers = {
1021
1147
 
1022
1148
  type CompletedOutputItem = { item: unknown; sourceBytes: number };
1023
1149
 
1150
+ function outputBearingSseEvent(event: { type?: unknown; output_index?: unknown } | null): boolean {
1151
+ if (!event || typeof event.type !== "string") return false;
1152
+ if (Object.hasOwn(event, "output_index")) return true;
1153
+ return /^response\.(?:output_item|content_part|output_text|refusal|reasoning_[^.]+|function_call_arguments|custom_tool_call_input|image_generation_call|code_interpreter_call|file_search_call|web_search_call|computer_call|tool_search_call)\./
1154
+ .test(event.type);
1155
+ }
1156
+
1024
1157
  function delimiterLengthAt(
1025
1158
  index: number,
1026
1159
  length: number,
@@ -1071,12 +1204,24 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
1071
1204
  let candidateBytes = 0;
1072
1205
  let discardingOversizedFrame = false;
1073
1206
  const reportFirstOutput = createFirstOutputReporter(handlers.onFirstOutput);
1074
- // Allocate reconstruction state only for persistence-capable inspectors.
1075
- const completedItemsByOutputIndex = handlers.onCompletedResponse
1207
+ const reconstructionPolicy = handlers.terminalReconstruction;
1208
+ if (reconstructionPolicy
1209
+ && (!Number.isSafeInteger(reconstructionPolicy.maxItems) || reconstructionPolicy.maxItems <= 0
1210
+ || !Number.isSafeInteger(reconstructionPolicy.maxSourceBytes) || reconstructionPolicy.maxSourceBytes <= 0)) {
1211
+ throw new RangeError("terminal reconstruction limits must be positive safe integers");
1212
+ }
1213
+ const maxCompletedItems = reconstructionPolicy?.maxItems ?? MAX_COMPLETED_OUTPUT_ITEMS;
1214
+ const maxCompletedItemSourceBytes = reconstructionPolicy?.maxSourceBytes
1215
+ ?? MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES;
1216
+ // Allocate reconstruction state only for snapshot-capable inspectors.
1217
+ const completedItemsByOutputIndex = handlers.onCompletedResponse || handlers.onTerminalResponse
1076
1218
  ? new Map<number, CompletedOutputItem>()
1077
1219
  : null;
1078
1220
  let aggregateItemBytes = 0;
1079
1221
  let reconstructionTainted = false;
1222
+ const observedOutputIndices = reconstructionPolicy?.requireCompleteOutputIndices
1223
+ ? new Set<number>()
1224
+ : null;
1080
1225
  let firstResponseId: string | undefined;
1081
1226
 
1082
1227
  const clearFrameState = (): void => {
@@ -1088,6 +1233,7 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
1088
1233
 
1089
1234
  const clearCompletedItems = (): void => {
1090
1235
  completedItemsByOutputIndex?.clear();
1236
+ observedOutputIndices?.clear();
1091
1237
  aggregateItemBytes = 0;
1092
1238
  reconstructionTainted = false;
1093
1239
  };
@@ -1151,18 +1297,26 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
1151
1297
  const retainCompletedItem = (index: number, item: unknown, sourceBytes: number): void => {
1152
1298
  const previous = completedItemsByOutputIndex!.get(index);
1153
1299
  if (previous) {
1300
+ if (reconstructionPolicy?.mergeSparseTerminalOutput === true) {
1301
+ // Repeated identical done frames are harmless, but a later frame must never rewrite an
1302
+ // already-completed index. The strict buffered path treats that contradiction as taint.
1303
+ if (nativeResponseFingerprint(previous.item) !== nativeResponseFingerprint(item)) {
1304
+ reconstructionTainted = true;
1305
+ }
1306
+ return;
1307
+ }
1154
1308
  aggregateItemBytes -= previous.sourceBytes;
1155
1309
  completedItemsByOutputIndex!.delete(index);
1156
1310
  }
1157
- if (sourceBytes > MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES) {
1311
+ if (sourceBytes > maxCompletedItemSourceBytes) {
1158
1312
  reconstructionTainted = true;
1159
1313
  inspectionCounters.itemCapEvictions += 1;
1160
1314
  return;
1161
1315
  }
1162
1316
  completedItemsByOutputIndex!.set(index, { item, sourceBytes });
1163
1317
  aggregateItemBytes += sourceBytes;
1164
- while (completedItemsByOutputIndex!.size > MAX_COMPLETED_OUTPUT_ITEMS
1165
- || aggregateItemBytes > MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES) {
1318
+ while (completedItemsByOutputIndex!.size > maxCompletedItems
1319
+ || aggregateItemBytes > maxCompletedItemSourceBytes) {
1166
1320
  let highestIndex = -1;
1167
1321
  for (const retainedIndex of completedItemsByOutputIndex!.keys()) {
1168
1322
  if (retainedIndex > highestIndex) highestIndex = retainedIndex;
@@ -1201,38 +1355,30 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
1201
1355
  // The other half of the same observation. A payload that did not parse still reached the
1202
1356
  // caller, so a consumer deciding whether anything has been emitted has to hear about it.
1203
1357
  if (handlers.onOpaquePayload && parsed === undefined) {
1204
- try { handlers.onOpaquePayload(); } catch { /* inspection must never throw into the pump */ }
1358
+ try { handlers.onOpaquePayload(payload); } catch { /* inspection must never throw into the pump */ }
1205
1359
  }
1206
1360
  reportFirstOutput.parsed(parsed);
1207
1361
  const status = terminalStatusFromParsed(parsed);
1208
- const policyTerminal = status === "failed"
1209
- && isPolicyRewriteType(parsed)
1210
- && cyberPolicyTerminalError(parsed) !== undefined;
1211
- if (status) sawTerminal = true;
1212
- if (!reported && handlers.onTerminal && status) {
1213
- try {
1214
- reported = true;
1215
- if (handlers.logCtx) {
1216
- handlers.logCtx.transportPhase = "terminal_sse";
1217
- handlers.logCtx.terminalSource = "upstream";
1218
- }
1219
- handlers.onTerminal(status, policyTerminal ? 400 : undefined);
1220
- } finally {
1221
- if (status === "failed" || status === "incomplete") clearCompletedItems();
1362
+ type ParsedSseEvent = { type?: unknown; output_index?: unknown; item?: unknown; response?: unknown };
1363
+ const parsedEvent = parsed && typeof parsed === "object" && !Array.isArray(parsed)
1364
+ ? parsed as ParsedSseEvent
1365
+ : null;
1366
+ if (observedOutputIndices && outputBearingSseEvent(parsedEvent)) {
1367
+ const outputIndex = parsedEvent?.output_index;
1368
+ if (Number.isInteger(outputIndex) && (outputIndex as number) >= 0
1369
+ && (outputIndex as number) < maxCompletedItems) {
1370
+ observedOutputIndices.add(outputIndex as number);
1371
+ } else {
1372
+ reconstructionTainted = true;
1222
1373
  }
1223
- } else if (status === "failed" || status === "incomplete") {
1224
- clearCompletedItems();
1225
1374
  }
1226
- if (handlers.onCompletedResponse) {
1227
- type ParsedSseEvent = { type?: unknown; output_index?: unknown; item?: unknown; response?: unknown };
1228
- const parsedEvent = parsed && typeof parsed === "object" && !Array.isArray(parsed)
1229
- ? parsed as ParsedSseEvent
1230
- : null;
1375
+ let reconstructedTerminalResponse: { id?: unknown; output?: unknown; status?: unknown } | null = null;
1376
+ if (completedItemsByOutputIndex) {
1231
1377
  const responseRecord = parsedEvent
1232
1378
  && typeof parsedEvent.response === "object"
1233
1379
  && parsedEvent.response !== null
1234
1380
  && !Array.isArray(parsedEvent.response)
1235
- ? parsedEvent.response as { id?: unknown }
1381
+ ? parsedEvent.response as { id?: unknown; output?: unknown; status?: unknown }
1236
1382
  : null;
1237
1383
  if (handlers.pinCompletedResponseIdToFirstSeen
1238
1384
  && responseRecord
@@ -1244,46 +1390,86 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
1244
1390
  && doneItem !== undefined
1245
1391
  && Number.isInteger(parsedEvent.output_index)
1246
1392
  && (parsedEvent.output_index as number) >= 0
1393
+ && (!observedOutputIndices || (parsedEvent.output_index as number) < maxCompletedItems)
1247
1394
  && typeof doneItem === "object"
1248
1395
  && doneItem !== null
1249
1396
  && !Array.isArray(doneItem)
1250
1397
  && typeof (doneItem as { type?: unknown }).type === "string") {
1251
1398
  retainCompletedItem(parsedEvent.output_index as number, doneItem, sourceBytes);
1252
1399
  }
1253
-
1254
- let response = completedResponseFromParsedEvent(parsedEvent);
1255
- if (response) {
1400
+ if (status && responseRecord) {
1401
+ reconstructedTerminalResponse = responseRecord;
1256
1402
  if (handlers.pinCompletedResponseIdToFirstSeen
1257
1403
  && firstResponseId !== undefined
1258
- && response.id !== firstResponseId) {
1259
- response = { ...response, id: firstResponseId };
1404
+ && reconstructedTerminalResponse.id !== firstResponseId) {
1405
+ reconstructedTerminalResponse = { ...reconstructedTerminalResponse, id: firstResponseId };
1260
1406
  }
1261
- // Authoritative output is a NON-EMPTY ARRAY only. Anything else
1262
- // (missing, null, scalar, object) keeps the historical backfill
1263
- // behavior so a malformed terminal cannot reach rememberResponseState
1264
- // and destroy continuation state (review C1-2).
1265
- const hasAuthoritativeOutput = Array.isArray(response.output)
1266
- && response.output.length > 0;
1267
- if (!hasAuthoritativeOutput && reconstructionTainted) {
1268
- clearCompletedItems();
1269
- return;
1407
+ const explicitTerminalOutput = reconstructedTerminalResponse.output;
1408
+ const terminalOutput = Array.isArray(explicitTerminalOutput)
1409
+ ? explicitTerminalOutput
1410
+ : null;
1411
+ if (explicitTerminalOutput !== undefined && explicitTerminalOutput !== null && !terminalOutput) {
1412
+ reconstructionTainted = true;
1270
1413
  }
1271
- if (!hasAuthoritativeOutput && completedItemsByOutputIndex!.size > 0) {
1272
- response = {
1273
- ...response,
1274
- output: [...completedItemsByOutputIndex!.entries()]
1414
+ if (terminalOutput && terminalOutput.length > maxCompletedItems) reconstructionTainted = true;
1415
+ const hasAuthoritativeOutput = terminalOutput !== null && terminalOutput.length > 0;
1416
+ if (reconstructionPolicy?.mergeSparseTerminalOutput === true) {
1417
+ if (terminalOutput?.some(item => !item || typeof item !== "object" || Array.isArray(item)
1418
+ || typeof (item as { type?: unknown }).type !== "string")) reconstructionTainted = true;
1419
+ let output: Record<string, unknown>[] = [];
1420
+ if (!reconstructionTainted) {
1421
+ try {
1422
+ output = nativeResponseOutput(
1423
+ new Map([...completedItemsByOutputIndex.entries()].map(([index, retained]) => [
1424
+ index,
1425
+ retained.item as Record<string, unknown>,
1426
+ ])),
1427
+ terminalOutput,
1428
+ );
1429
+ } catch {
1430
+ reconstructionTainted = true;
1431
+ }
1432
+ }
1433
+ if (output.length > maxCompletedItems) reconstructionTainted = true;
1434
+ for (const index of observedOutputIndices ?? []) {
1435
+ if (index >= output.length) reconstructionTainted = true;
1436
+ }
1437
+ reconstructedTerminalResponse = reconstructionTainted
1438
+ ? null
1439
+ : { ...reconstructedTerminalResponse, output };
1440
+ } else if (!hasAuthoritativeOutput && reconstructionTainted) {
1441
+ reconstructedTerminalResponse = null;
1442
+ } else if (!hasAuthoritativeOutput && completedItemsByOutputIndex.size > 0) {
1443
+ reconstructedTerminalResponse = {
1444
+ ...reconstructedTerminalResponse,
1445
+ output: [...completedItemsByOutputIndex.entries()]
1275
1446
  .sort(([left], [right]) => left - right)
1276
1447
  .map(([, retained]) => retained.item),
1277
1448
  };
1278
1449
  }
1279
- try {
1280
- handlers.onCompletedResponse(response);
1281
- } finally {
1282
- clearCompletedItems();
1450
+ }
1451
+ }
1452
+ const policyTerminal = status === "failed"
1453
+ && isPolicyRewriteType(parsed)
1454
+ && cyberPolicyTerminalError(parsed) !== undefined;
1455
+ if (status) sawTerminal = true;
1456
+ try {
1457
+ if (!reported && handlers.onTerminal && status) {
1458
+ reported = true;
1459
+ if (handlers.logCtx) {
1460
+ handlers.logCtx.transportPhase = "terminal_sse";
1461
+ handlers.logCtx.terminalSource = "upstream";
1283
1462
  }
1284
- } else if (parsedEvent?.type === "response.completed") {
1285
- clearCompletedItems();
1463
+ handlers.onTerminal(status, policyTerminal ? 400 : undefined);
1464
+ }
1465
+ if (status === "completed" && reconstructedTerminalResponse && handlers.onCompletedResponse) {
1466
+ handlers.onCompletedResponse(reconstructedTerminalResponse);
1286
1467
  }
1468
+ if (status && reconstructedTerminalResponse && handlers.onTerminalResponse) {
1469
+ handlers.onTerminalResponse(status, reconstructedTerminalResponse);
1470
+ }
1471
+ } finally {
1472
+ if (status) clearCompletedItems();
1287
1473
  }
1288
1474
  };
1289
1475
 
@@ -1294,7 +1480,7 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
1294
1480
  }
1295
1481
  const sourceBytes = candidateBytes;
1296
1482
  const frame = takeCandidate();
1297
- if (reported && !handlers.onCompletedResponse) return;
1483
+ if (reported && !handlers.onCompletedResponse && !handlers.onTerminalResponse) return;
1298
1484
  const decoded = decoder!.decode(frame);
1299
1485
  scanPayload(sseDataPayload(decoded), sourceBytes);
1300
1486
  };
@@ -35,6 +35,7 @@ import { bindRouteReasoningReplayScope } from "./core-replay";
35
35
  import {
36
36
  ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
37
37
  rotateAnthropicAccountOn429,
38
+ recordAnthropicAccount429,
38
39
  getAnthropicPoolAccessSnapshot,
39
40
  formatAnthropicProviderForLog,
40
41
  } from "../../oauth/anthropic-routing";
@@ -425,6 +426,11 @@ export function createAdapterContinuations(
425
426
  }
426
427
  }
427
428
  }
429
+ if (response.status === 429 && transportState.anthropicPoolAccountId
430
+ && transportState.anthropicPoolFailovers >= ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST) {
431
+ recordAnthropicAccount429(config, transportState.anthropicPoolAccountId,
432
+ response.headers.get("retry-after"), Date.now(), response.headers);
433
+ }
428
434
  // Generic OAuth rotation for the continuation loop. The streaming loop grew this arm with
429
435
  // #2568 and this one did not, so an xAI/Cursor/Kimi/Copilot/Antigravity/Nous continuation
430
436
  // 429 stayed terminal even with failover fully active -- the same class of divergence the