@coseung2/opencodex 2.8.0-cs.13 → 2.8.0-cs.15

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 (60) hide show
  1. package/gui/dist/assets/index-BhXIu7c0.js +67 -0
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +3 -3
  4. package/packages/ocx-notch/README.md +2 -1
  5. package/src/adapters/cursor/discovery.ts +6 -2
  6. package/src/adapters/cursor/effort-map.ts +3 -0
  7. package/src/adapters/google-antigravity-replay.ts +24 -0
  8. package/src/adapters/google.ts +16 -11
  9. package/src/chat/inbound.ts +5 -11
  10. package/src/cli/account-api.ts +9 -1
  11. package/src/cli/account-extended.ts +4 -1
  12. package/src/codex/account-label.ts +14 -1
  13. package/src/codex/account-lifecycle.ts +12 -1
  14. package/src/codex/account-namespaces.ts +21 -0
  15. package/src/codex/account-priority.ts +49 -0
  16. package/src/codex/account-store.ts +2 -1
  17. package/src/codex/auth-api.ts +108 -17
  18. package/src/codex/auth-context.ts +61 -16
  19. package/src/codex/catalog/metadata.ts +34 -12
  20. package/src/codex/catalog/parsing.ts +8 -1
  21. package/src/codex/catalog/provider-fetch.ts +24 -6
  22. package/src/codex/catalog.ts +1 -1
  23. package/src/codex/pool-rotation.ts +51 -4
  24. package/src/codex/quota.ts +154 -35
  25. package/src/codex/routing.ts +139 -33
  26. package/src/codex/warmup.ts +193 -85
  27. package/src/config.ts +84 -1
  28. package/src/lib/bounded-body.ts +13 -6
  29. package/src/lib/bun-stream-caps.ts +5 -6
  30. package/src/lib/redact.ts +13 -0
  31. package/src/oauth/index.ts +79 -12
  32. package/src/oauth/log.ts +3 -1
  33. package/src/oauth/store.ts +31 -8
  34. package/src/providers/antigravity-models.ts +53 -24
  35. package/src/providers/codex-capacity.ts +303 -0
  36. package/src/providers/model-rename-migration.ts +147 -0
  37. package/src/providers/model-rename-startup.ts +29 -0
  38. package/src/providers/quota.ts +126 -16
  39. package/src/providers/registry.ts +258 -38
  40. package/src/responses/parser.ts +19 -12
  41. package/src/responses/spill-store.ts +14 -1
  42. package/src/responses/state.ts +108 -14
  43. package/src/server/index.ts +9 -1
  44. package/src/server/management/logs-usage-routes.ts +1 -0
  45. package/src/server/management/oauth-account-routes.ts +8 -1
  46. package/src/server/relay.ts +10 -42
  47. package/src/server/request-log.ts +42 -1
  48. package/src/server/responses/compact.ts +16 -4
  49. package/src/server/responses/core.ts +217 -59
  50. package/src/server/responses/empty-completion-guard.ts +275 -0
  51. package/src/server/responses/encrypted-payload.ts +54 -39
  52. package/src/server/responses/fetch-helpers.ts +24 -3
  53. package/src/server/responses/ws-upstream.ts +318 -0
  54. package/src/server/sse-frame-buffer.ts +292 -0
  55. package/src/server/ws-bridge.ts +17 -11
  56. package/src/types.ts +8 -0
  57. package/src/usage/log.ts +24 -0
  58. package/src/usage/summary.ts +152 -2
  59. package/vendor/ocx-notch/win32-x64/ocx-notch.exe +0 -0
  60. package/gui/dist/assets/index-BucjyD4I.js +0 -67
@@ -45,9 +45,21 @@ export interface ResponseSpillRef {
45
45
  payloadBytes: number;
46
46
  }
47
47
 
48
+ /** Single-spill ceiling enforced at admission and replay. */
49
+ export const MAX_RESPONSE_SPILL_PAYLOAD_BYTES = 256 * 1024 * 1024;
50
+ let spillPayloadCapOverride: number | null = null;
51
+
52
+ export function setResponseSpillPayloadCapForTests(bytes: number | null): void {
53
+ spillPayloadCapOverride = bytes;
54
+ }
55
+
56
+ export function responseSpillPayloadCap(): number {
57
+ return spillPayloadCapOverride ?? MAX_RESPONSE_SPILL_PAYLOAD_BYTES;
58
+ }
59
+
48
60
  export type ResponseSpillReadResult =
49
61
  | { ok: true; payload: ResponseSpillPayload }
50
- | { ok: false; reason: "missing" | "corrupt" };
62
+ | { ok: false; reason: "missing" | "corrupt" | "too_large" };
51
63
 
52
64
  export interface ResponseSpillCleanupResult {
53
65
  scanned: number;
@@ -306,6 +318,7 @@ export function writeResponseSpillDurably(
306
318
 
307
319
  export function readResponseSpill(responseId: string, ref: ResponseSpillRef): ResponseSpillReadResult {
308
320
  if (!validSpillRef(ref)) return { ok: false, reason: "corrupt" };
321
+ if (ref.payloadBytes > responseSpillPayloadCap()) return { ok: false, reason: "too_large" };
309
322
  const match = OWNED_SPILL_NAME.exec(ref.fileName);
310
323
  if (!match
311
324
  || match[2] !== sha256(responseId).slice(0, 12)
@@ -1,4 +1,4 @@
1
- import { chmodSync, existsSync, lstatSync, mkdirSync, opendirSync, readFileSync, rmSync, unlinkSync } from "node:fs";
1
+ import { chmodSync, existsSync, lstatSync, mkdirSync, opendirSync, readFileSync, rmSync, statSync, unlinkSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { isDeepStrictEqual } from "node:util";
4
4
  import { atomicWriteFileAsync, getConfigDir } from "../config";
@@ -10,6 +10,7 @@ import {
10
10
  readResponseSpill,
11
11
  recoverOrphanedResponseSpills,
12
12
  responseSpillDirectory,
13
+ responseSpillPayloadCap,
13
14
  type ResponseSpillRef,
14
15
  writeResponseSpillDurably,
15
16
  } from "./spill-store";
@@ -24,6 +25,7 @@ export const MAX_STORED_RESPONSE_BYTES = 64 * 1024 * 1024;
24
25
  /** Legacy snapshot selection only. Spill demotion is governed solely by the RAM cap above. */
25
26
  const SNAPSHOT_ENTRY_MAX_BYTES = 2 * 1024 * 1024;
26
27
  const SNAPSHOT_TOTAL_MAX_BYTES = 24 * 1024 * 1024;
28
+ const SNAPSHOT_FILE_MAX_BYTES = 32 * 1024 * 1024;
27
29
  const STALE_TEMP_GRACE_MS = 15 * 60 * 1_000;
28
30
  const STALE_TEMP_MAX_ENTRIES = 4_096;
29
31
  const STALE_TEMP_MAX_CLEANUPS = 512;
@@ -57,7 +59,7 @@ type ResidentInput = Omit<ResidentResponseState, "kind" | "sizeBytes">;
57
59
 
58
60
  export type PreviousResponseReplayFailure = {
59
61
  code: "previous_response_not_found";
60
- reason: "spill_missing" | "spill_corrupt" | "spill_failed";
62
+ reason: "spill_missing" | "spill_corrupt" | "spill_failed" | "spill_too_large";
61
63
  };
62
64
 
63
65
  const states = new Map<string, StoredResponseState>();
@@ -68,6 +70,11 @@ let oldestResidentAt: number | null = null;
68
70
  let byteCapOverride: number | null = null;
69
71
  let stateRevision = 0;
70
72
  const spillCounters = { writes: 0, writeFailures: 0, readFailures: 0 };
73
+ const admissionCounters = { directSpills: 0, oversizedDrops: 0, snapshotOversizedRefusals: 0 };
74
+
75
+ export function responseAdmissionCountersForTests(): Readonly<typeof admissionCounters> {
76
+ return admissionCounters;
77
+ }
71
78
  // Superseded spill generations awaiting a durable snapshot before unlink
72
79
  // (review C1-1: unlinking at swap time races a crash against the debounced
73
80
  // snapshot — the reloaded OLD stub would point at a deleted file).
@@ -178,12 +185,25 @@ function deleteEntry(id: string, options: { deleteSpill?: boolean } = {}): void
178
185
  if (options.deleteSpill !== false) deleteOwnedSpills(existing);
179
186
  }
180
187
 
181
- function replaceWithSpillFailure(id: string, expected?: StoredResponseState): void {
188
+ function replaceWithSpillFailure(
189
+ id: string,
190
+ expected?: StoredResponseState,
191
+ options: { deferSpillUnlink?: boolean } = {},
192
+ ): void {
182
193
  const existing = states.get(id);
183
194
  if (expected && existing !== expected) return;
184
195
  const failed = tombstone(id, expected?.createdAt ?? existing?.createdAt ?? now());
185
196
  if (replaceMapEntry(id, failed, expected)) {
186
- if (existing) deleteOwnedSpills(existing);
197
+ if (existing) {
198
+ if (options.deferSpillUnlink && existing.kind === "spill") {
199
+ pendingSpillUnlinks.push(existing.spill);
200
+ while (pendingSpillUnlinks.length > PENDING_SPILL_UNLINKS_MAX) {
201
+ deleteResponseSpill(pendingSpillUnlinks.shift()!);
202
+ }
203
+ } else {
204
+ deleteOwnedSpills(existing);
205
+ }
206
+ }
187
207
  }
188
208
  }
189
209
 
@@ -237,7 +257,7 @@ function replaceSpillEntryAtomically(
237
257
  }
238
258
  } catch {
239
259
  spillCounters.writeFailures += 1;
240
- replaceWithSpillFailure(id, expected);
260
+ replaceWithSpillFailure(id, expected, { deferSpillUnlink: true });
241
261
  }
242
262
  }
243
263
 
@@ -252,6 +272,11 @@ function setResidentEntry(id: string, entry: ResidentInput): void {
252
272
  pruneResponses();
253
273
  return;
254
274
  }
275
+ if (candidate.sizeBytes > byteCap()) {
276
+ admitOversizedCandidate(id, candidate, expected);
277
+ pruneResponses();
278
+ return;
279
+ }
255
280
  if (expected?.kind === "spill") {
256
281
  replaceSpillEntryAtomically(id, expected, candidate);
257
282
  pruneResponses();
@@ -261,6 +286,55 @@ function setResidentEntry(id: string, entry: ResidentInput): void {
261
286
  pruneResponses();
262
287
  }
263
288
 
289
+ /** Admit a candidate that can never fit in the resident map directly to spill. */
290
+ function admitOversizedCandidate(
291
+ id: string,
292
+ candidate: ResidentResponseState,
293
+ expected?: StoredResponseState,
294
+ ): void {
295
+ if (candidate.sizeBytes > responseSpillPayloadCap()) {
296
+ admissionCounters.oversizedDrops += 1;
297
+ replaceWithSpillFailure(id, expected, { deferSpillUnlink: true });
298
+ return;
299
+ }
300
+ try {
301
+ const ref = writeResponseSpillDurably(id, {
302
+ createdAt: candidate.createdAt,
303
+ items: candidate.items,
304
+ ...(candidate.providers ? { providers: candidate.providers } : {}),
305
+ });
306
+ if (ref.payloadBytes > responseSpillPayloadCap()) {
307
+ deleteResponseSpill(ref);
308
+ admissionCounters.oversizedDrops += 1;
309
+ replaceWithSpillFailure(id, expected, { deferSpillUnlink: true });
310
+ return;
311
+ }
312
+ const base: Omit<SpilledResponseState, "sizeBytes"> = {
313
+ kind: "spill",
314
+ createdAt: candidate.createdAt,
315
+ ...(candidate.providers ? { providers: candidate.providers } : {}),
316
+ spill: ref,
317
+ };
318
+ const next: SpilledResponseState = { ...base, sizeBytes: stubSize(id, base) };
319
+ if (!replaceMapEntry(id, next, expected)) {
320
+ deleteResponseSpill(ref);
321
+ return;
322
+ }
323
+ spillCounters.writes += 1;
324
+ admissionCounters.directSpills += 1;
325
+ noteStubSwapForTest();
326
+ if (expected?.kind === "spill") {
327
+ pendingSpillUnlinks.push(expected.spill);
328
+ while (pendingSpillUnlinks.length > PENDING_SPILL_UNLINKS_MAX) {
329
+ deleteResponseSpill(pendingSpillUnlinks.shift()!);
330
+ }
331
+ }
332
+ } catch {
333
+ spillCounters.writeFailures += 1;
334
+ replaceWithSpillFailure(id, expected, { deferSpillUnlink: true });
335
+ }
336
+ }
337
+
264
338
  // Expansion provenance must stay proxy-private: a WeakMap distinguishes replayed history from the
265
339
  // newly appended input suffix without adding an unknown field that native passthrough could send
266
340
  // upstream. The parser uses this boundary to acknowledge historical compaction markers exactly once.
@@ -335,8 +409,15 @@ function loadSnapshotEntry(id: string, value: unknown): void {
335
409
  items: rec.items,
336
410
  ...(providers ? { providers } : {}),
337
411
  });
338
- if (resident) replaceMapEntry(id, resident);
339
- else replaceMapEntry(id, tombstone(id, rec.createdAt));
412
+ if (!resident) {
413
+ replaceMapEntry(id, tombstone(id, rec.createdAt));
414
+ return;
415
+ }
416
+ if (resident.sizeBytes > byteCap()) {
417
+ admitOversizedCandidate(id, resident, undefined);
418
+ return;
419
+ }
420
+ replaceMapEntry(id, resident);
340
421
  }
341
422
 
342
423
  export interface ResponseStateTempRecoveryResult {
@@ -462,11 +543,20 @@ function ensureLoaded(): void {
462
543
  }
463
544
  try {
464
545
  if (existsSync(path)) {
465
- const raw = JSON.parse(readFileSync(path, "utf-8")) as { version?: unknown; states?: unknown };
466
- if ((raw.version === 1 || raw.version === 2) && Array.isArray(raw.states)) {
467
- for (const entry of raw.states) {
468
- if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string") continue;
469
- loadSnapshotEntry(entry[0], entry[1]);
546
+ // Follow the target because readFileSync follows it too, but parse only a
547
+ // bounded regular file (never a FIFO/device or an oversized planted file).
548
+ const stat = statSync(path);
549
+ if (!stat.isFile()) {
550
+ // Unsupported target: start empty.
551
+ } else if (stat.size > SNAPSHOT_FILE_MAX_BYTES) {
552
+ admissionCounters.snapshotOversizedRefusals += 1;
553
+ } else {
554
+ const raw = JSON.parse(readFileSync(path, "utf-8")) as { version?: unknown; states?: unknown };
555
+ if ((raw.version === 1 || raw.version === 2) && Array.isArray(raw.states)) {
556
+ for (const entry of raw.states) {
557
+ if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string") continue;
558
+ loadSnapshotEntry(entry[0], entry[1]);
559
+ }
470
560
  }
471
561
  }
472
562
  }
@@ -505,7 +595,7 @@ async function writeBoundedSnapshot(path: string): Promise<SnapshotWriteOutcome>
505
595
  persistable = smallState;
506
596
  }
507
597
  const persistEntry: [string, unknown] = [id, persistable];
508
- const size = JSON.stringify(persistEntry).length;
598
+ const size = Buffer.byteLength(JSON.stringify(persistEntry), "utf8");
509
599
  if (state.kind === "resident" && size > SNAPSHOT_ENTRY_MAX_BYTES) continue;
510
600
  if (total + size > SNAPSHOT_TOTAL_MAX_BYTES) break;
511
601
  total += size;
@@ -685,7 +775,11 @@ function materializeEntry(
685
775
  spillCounters.readFailures += 1;
686
776
  const failure: PreviousResponseReplayFailure = {
687
777
  code: "previous_response_not_found",
688
- reason: result.reason === "missing" ? "spill_missing" : "spill_corrupt",
778
+ reason: result.reason === "missing"
779
+ ? "spill_missing"
780
+ : result.reason === "too_large"
781
+ ? "spill_too_large"
782
+ : "spill_corrupt",
689
783
  };
690
784
  replaceWithSpillFailure(id, entry);
691
785
  schedulePersist();
@@ -41,9 +41,11 @@ import { setStorageCleanupPolicyJobLiveApply } from "../storage/policy-job";
41
41
  import { scheduleStorageCleanupStartupRun, startStorageCleanupScheduler } from "../storage/policy-scheduler";
42
42
  import { runOpenAiTierStartupMigration } from "../providers/openai-tier-startup";
43
43
  import { runAlibabaRegionStartupMigration } from "../providers/alibaba-region-startup";
44
+ import { runModelRenameStartupMigration } from "../providers/model-rename-startup";
44
45
  import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
45
46
  import { providerCodexAccountMode } from "../providers/registry";
46
47
  import type { StorageCleanupPolicy } from "../types";
48
+ import { MAX_DECOMPRESSED_BODY_BYTES } from "./request-decompress";
47
49
  import {
48
50
  CodexAccountCooldownError,
49
51
  cooldownErrorMessage,
@@ -272,7 +274,9 @@ function attachLiveSidebandUpstream(ws: ServerWebSocket<WsData>): void {
272
274
  // export function relaySseWithHeartbeat
273
275
 
274
276
  export function startServer(port?: number) {
275
- const config = runAlibabaRegionStartupMigration(runOpenAiTierStartupMigration(loadConfig()));
277
+ const config = runModelRenameStartupMigration(
278
+ runAlibabaRegionStartupMigration(runOpenAiTierStartupMigration(loadConfig())),
279
+ );
276
280
  setLiveStateStoreConfig(config);
277
281
  applyProxyEnv(config);
278
282
  assertServerAuthConfig(config);
@@ -392,6 +396,10 @@ export function startServer(port?: number) {
392
396
  port: listenPort,
393
397
  hostname: bindHost,
394
398
  idleTimeout: 255,
399
+ // Keep Bun's listener admission ceiling aligned with the bounded
400
+ // decompression reader. Bun otherwise rejects valid large Codex turns at
401
+ // its lower default before the application can enforce the 256 MiB cap.
402
+ maxRequestBodySize: MAX_DECOMPRESSED_BODY_BYTES,
395
403
  async fetch(req, requestServer): Promise<Response> {
396
404
  const url = new URL(req.url);
397
405
  markActivity(`${req.method} ${url.pathname}`);
@@ -243,6 +243,7 @@ export async function handleLogsUsageRoutes(ctx: ManagementContext): Promise<Res
243
243
  days: [],
244
244
  models: [],
245
245
  providers: [],
246
+ accounts: [],
246
247
  historyTruncated: false,
247
248
  truncatedPrefixBytes: 0,
248
249
  entriesTruncated: false,
@@ -19,6 +19,7 @@ import {
19
19
  getLoginStatus,
20
20
  isPublicOAuthProvider,
21
21
  listOAuthProviders,
22
+ publicOAuthAuthenticationErrorMessage,
22
23
  startLoginFlow,
23
24
  submitManualLoginCode,
24
25
  } from "../../oauth";
@@ -169,7 +170,13 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
169
170
  return jsonResponse({ url: authUrl, instructions, deviceCode });
170
171
  } catch (err) {
171
172
  if (err instanceof OAuthMutationBusyError) throw err;
172
- return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, 409);
173
+ const message = err instanceof Error ? err.message : String(err);
174
+ const duplicateLoginMessage = `A login for ${provider} is already in progress`;
175
+ return jsonResponse({
176
+ error: message === duplicateLoginMessage
177
+ ? duplicateLoginMessage
178
+ : publicOAuthAuthenticationErrorMessage(err),
179
+ }, 409);
173
180
  }
174
181
  }
175
182
 
@@ -6,7 +6,6 @@ import {
6
6
  addFinalRequestLog,
7
7
  httpStatusForRequestLogTerminal,
8
8
  inspectResponseLogJson,
9
- inspectResponseLogSsePayload,
10
9
  inspectResponseLogSsePayloadParsed,
11
10
  recordFirstOutput,
12
11
  type RequestLogContext,
@@ -245,10 +244,7 @@ export function trackSseForRequestLog(
245
244
  onFirstOutput?: () => void,
246
245
  ): ReadableStream<Uint8Array> {
247
246
  const reader = body.getReader();
248
- const decoder = new TextDecoder();
249
- let buffer = "";
250
247
  let terminalReported = false;
251
- const reportFirstOutput = createFirstOutputReporter(onFirstOutput);
252
248
 
253
249
  const reportTerminal = (status: ResponsesTerminalStatus) => {
254
250
  if (terminalReported) return;
@@ -256,42 +252,29 @@ export function trackSseForRequestLog(
256
252
  onTerminal(status);
257
253
  };
258
254
 
259
- const inspectPayload = (payload: string | null) => {
260
- if (!payload) return;
261
- if (logCtx) inspectResponseLogSsePayload(logCtx, payload);
262
- reportFirstOutput.payload(payload);
263
- const status = terminalStatusFromSsePayload(payload);
264
- if (status) reportTerminal(status);
265
- };
266
-
267
- const inspectChunk = (value: Uint8Array) => {
268
- buffer += decoder.decode(value, { stream: true });
269
- let next: { block: string; rest: string } | null;
270
- while ((next = nextSseBlock(buffer))) {
271
- buffer = next.rest;
272
- inspectPayload(sseDataPayload(next.block));
273
- }
274
- };
255
+ const inspector = createSseInspector({ onTerminal: reportTerminal, logCtx, onFirstOutput });
275
256
 
276
257
  return new ReadableStream<Uint8Array>({
277
258
  async pull(controller) {
278
259
  try {
279
260
  const { done, value } = await reader.read();
280
261
  if (done) {
281
- buffer += decoder.decode();
282
- if (buffer.trim()) inspectPayload(sseDataPayload(buffer));
262
+ inspector.finish();
283
263
  if (!terminalReported) reportTerminal("incomplete");
264
+ inspector.dispose();
284
265
  controller.close();
285
266
  return;
286
267
  }
287
- inspectChunk(value);
268
+ inspector.feed(value);
288
269
  controller.enqueue(value);
289
270
  } catch (err) {
290
271
  if (!terminalReported) reportTerminal("incomplete");
272
+ inspector.dispose();
291
273
  try { controller.error(err); } catch { /* already torn down */ }
292
274
  }
293
275
  },
294
276
  cancel(reason) {
277
+ inspector.dispose();
295
278
  onCancel();
296
279
  reader.cancel(reason).catch(() => {});
297
280
  },
@@ -402,13 +385,11 @@ export function relaySseWithHeartbeat(
402
385
  ): ReadableStream<Uint8Array> | null {
403
386
  if (!body) return null;
404
387
  const reader = body.getReader();
405
- const decoder = new TextDecoder();
406
388
  const heartbeat = new TextEncoder().encode(": opencodex keepalive\n\n");
407
389
  let timer: ReturnType<typeof setInterval> | undefined;
408
390
  let closed = false;
409
391
  let clientCancelled = false;
410
392
  let terminalReported = false;
411
- let buffer = "";
412
393
 
413
394
  const reportTerminal = (status: ResponsesTerminalStatus) => {
414
395
  if (terminalReported || clientCancelled || closed) return;
@@ -416,24 +397,12 @@ export function relaySseWithHeartbeat(
416
397
  onTerminal?.(status);
417
398
  };
418
399
 
419
- const inspectPayload = (payload: string | null) => {
420
- if (!payload) return;
421
- const status = terminalStatusFromSsePayload(payload);
422
- if (status) reportTerminal(status);
423
- };
424
-
425
- const inspectChunk = (value: Uint8Array) => {
426
- buffer += decoder.decode(value, { stream: true });
427
- let next: { block: string; rest: string } | null;
428
- while ((next = nextSseBlock(buffer))) {
429
- buffer = next.rest;
430
- inspectPayload(sseDataPayload(next.block));
431
- }
432
- };
400
+ const inspector = createSseInspector({ onTerminal: reportTerminal });
433
401
 
434
402
  const cleanup = () => {
435
403
  if (closed) return;
436
404
  closed = true;
405
+ inspector.dispose();
437
406
  if (timer) clearInterval(timer);
438
407
  timer = undefined;
439
408
  options?.onDone?.();
@@ -455,14 +424,13 @@ export function relaySseWithHeartbeat(
455
424
  try {
456
425
  const { done, value } = await reader.read();
457
426
  if (done) {
458
- buffer += decoder.decode();
459
- if (buffer.trim()) inspectPayload(sseDataPayload(buffer));
427
+ inspector.finish();
460
428
  if (!terminalReported && !clientCancelled) reportTerminal("incomplete");
461
429
  cleanup();
462
430
  controller.close();
463
431
  return;
464
432
  }
465
- inspectChunk(value);
433
+ inspector.feed(value);
466
434
  controller.enqueue(value);
467
435
  } catch (err) {
468
436
  if (!clientCancelled) reportTerminal("incomplete");
@@ -9,9 +9,10 @@ import { CODEX_CONFIG_PATH, readRootTomlString } from "../codex/paths";
9
9
  import { readCodexCatalogPath } from "../codex/catalog";
10
10
  import type { OcxUsage } from "../types";
11
11
  import type { AdapterRequest } from "../adapters/base";
12
- import { redactSecretString } from "../lib/redact";
12
+ import { redactSecretString, sanitizeLogMetadataString } from "../lib/redact";
13
13
  import {
14
14
  appendUsageEntry,
15
+ isCodexUsageAccountLogLabel,
15
16
  isKnownAdmissionKind,
16
17
  isKnownInboundProtocol,
17
18
  isKnownUsageSurface,
@@ -37,6 +38,8 @@ import { enforceAppOwnedMemoryBudget, type RetainedStoreSnapshot } from "../lib/
37
38
  export interface RequestLogContext {
38
39
  model: string;
39
40
  provider: string;
41
+ /** Stable non-PII Codex Pool account identity for durable usage attribution. */
42
+ accountLogLabel?: string;
40
43
  /** TTFT: ms from request start to the first non-empty model output delta (WP4, devlog 040). */
41
44
  firstOutputMs?: number;
42
45
  /** Best-effort chat/session correlation for Logs grouping (#330). Opaque; omit when unknown. */
@@ -53,6 +56,8 @@ export interface RequestLogContext {
53
56
  * since both leave it undefined. */
54
57
  inboundProtocol?: "responses" | "chat" | "messages";
55
58
  requestedModel?: string;
59
+ /** Original helper model when shadow-call interception rewrote the request. */
60
+ shadowCallRewrittenFrom?: string;
56
61
  /** Internal structural combo identity; omitted from RequestLogEntry/JSONL. */
57
62
  comboId?: string;
58
63
  requestedEffort?: string;
@@ -102,6 +107,7 @@ export interface RequestLogEntry {
102
107
  timestamp: number;
103
108
  model: string;
104
109
  provider: string;
110
+ accountLogLabel?: string;
105
111
  /** TTFT: ms from request start to the first non-empty model output delta; unset for non-streaming/tool-only. */
106
112
  firstOutputMs?: number;
107
113
  surface?: "claude" | "claude-desktop" | "grok";
@@ -118,6 +124,8 @@ export interface RequestLogEntry {
118
124
  /** Best-effort chat/session correlation for Logs grouping (#330). */
119
125
  conversationId?: string;
120
126
  requestedModel?: string;
127
+ /** Original helper model when shadow-call interception rewrote the request. */
128
+ shadowCallRewrittenFrom?: string;
121
129
  requestedEffort?: string;
122
130
  effectiveEffort?: string;
123
131
  reasoningWireField?: string;
@@ -219,10 +227,16 @@ export function requestLogEntryFromPersistedUsage(entry: PersistedUsageEntry): R
219
227
  timestamp: entry.timestamp,
220
228
  model: entry.model,
221
229
  provider: entry.provider,
230
+ ...(isCodexUsageAccountLogLabel(entry.accountLogLabel)
231
+ ? { accountLogLabel: entry.accountLogLabel }
232
+ : {}),
222
233
  ...(entry.firstOutputMs !== undefined ? { firstOutputMs: entry.firstOutputMs } : {}),
223
234
  ...(isKnownUsageSurface(entry.surface) ? { surface: entry.surface } : {}),
224
235
  ...(entry.conversationId ? { conversationId: entry.conversationId } : {}),
225
236
  ...(entry.requestedModel ? { requestedModel: entry.requestedModel } : {}),
237
+ ...(entry.shadowCallRewrittenFrom
238
+ ? { shadowCallRewrittenFrom: entry.shadowCallRewrittenFrom }
239
+ : {}),
226
240
  ...(entry.requestedEffort ? { requestedEffort: entry.requestedEffort } : {}),
227
241
  ...(entry.effectiveEffort ? { effectiveEffort: entry.effectiveEffort } : {}),
228
242
  ...(entry.reasoningWireField ? { reasoningWireField: entry.reasoningWireField } : {}),
@@ -281,6 +295,14 @@ export function hydrateRequestLogsFromDisk(
281
295
  }
282
296
 
283
297
  export function addRequestLog(entry: RequestLogEntry) {
298
+ const safeEntry = { ...entry };
299
+ if (safeEntry.accountLogLabel !== undefined && !isCodexUsageAccountLogLabel(safeEntry.accountLogLabel)) {
300
+ delete safeEntry.accountLogLabel;
301
+ }
302
+ const shadowCallRewrittenFrom = sanitizeLogMetadataString(safeEntry.shadowCallRewrittenFrom);
303
+ if (shadowCallRewrittenFrom) safeEntry.shadowCallRewrittenFrom = shadowCallRewrittenFrom;
304
+ else delete safeEntry.shadowCallRewrittenFrom;
305
+ entry = safeEntry;
284
306
  retainRequestLogEntry(entry);
285
307
  try {
286
308
  // Failure diagnostics survive the 200-entry ring buffer by riding the persisted
@@ -299,6 +321,9 @@ export function addRequestLog(entry: RequestLogEntry) {
299
321
  timestamp: entry.timestamp,
300
322
  provider: entry.provider,
301
323
  model: entry.model,
324
+ ...(isCodexUsageAccountLogLabel(entry.accountLogLabel)
325
+ ? { accountLogLabel: entry.accountLogLabel }
326
+ : {}),
302
327
  ...(isKnownUsageSurface(entry.surface) ? { surface: entry.surface } : {}),
303
328
  // This function REBUILDS the persisted row field by field rather than
304
329
  // spreading it, so a field missing here reaches /api/logs and never
@@ -309,6 +334,9 @@ export function addRequestLog(entry: RequestLogEntry) {
309
334
  ...(entry.conversationId ? { conversationId: entry.conversationId } : {}),
310
335
  ...(entry.resolvedModel ? { resolvedModel: entry.resolvedModel } : {}),
311
336
  ...(entry.requestedModel ? { requestedModel: entry.requestedModel } : {}),
337
+ ...(entry.shadowCallRewrittenFrom
338
+ ? { shadowCallRewrittenFrom: entry.shadowCallRewrittenFrom }
339
+ : {}),
312
340
  ...(entry.requestedEffort ? { requestedEffort: entry.requestedEffort } : {}),
313
341
  ...(entry.effectiveEffort ? { effectiveEffort: entry.effectiveEffort } : {}),
314
342
  ...(entry.reasoningWireField ? { reasoningWireField: entry.reasoningWireField } : {}),
@@ -786,12 +814,18 @@ export function addFinalRequestLog(
786
814
  timestamp: start,
787
815
  model: isCombo ? logCtx.requestedModel! : logCtx.model,
788
816
  provider: isCombo ? "combo" : logCtx.provider,
817
+ ...(isCodexUsageAccountLogLabel(logCtx.accountLogLabel)
818
+ ? { accountLogLabel: logCtx.accountLogLabel }
819
+ : {}),
789
820
  ...(logCtx.surface ? { surface: logCtx.surface } : {}),
790
821
  ...(logCtx.apiKeyId ? { apiKeyId: logCtx.apiKeyId } : {}),
791
822
  ...(logCtx.admissionKind ? { admissionKind: logCtx.admissionKind } : {}),
792
823
  ...(logCtx.inboundProtocol ? { inboundProtocol: logCtx.inboundProtocol } : {}),
793
824
  ...(logCtx.conversationId ? { conversationId: logCtx.conversationId } : {}),
794
825
  ...(logCtx.requestedModel ? { requestedModel: logCtx.requestedModel } : {}),
826
+ ...(sanitizeLogMetadataString(logCtx.shadowCallRewrittenFrom)
827
+ ? { shadowCallRewrittenFrom: sanitizeLogMetadataString(logCtx.shadowCallRewrittenFrom)! }
828
+ : {}),
795
829
  ...(logCtx.requestedEffort ? { requestedEffort: logCtx.requestedEffort } : {}),
796
830
  ...(logCtx.effectiveEffort ? { effectiveEffort: logCtx.effectiveEffort } : {}),
797
831
  ...(logCtx.reasoningWireField ? { reasoningWireField: logCtx.reasoningWireField } : {}),
@@ -850,6 +884,10 @@ export function filterRequestLogs(logs: RequestLogEntry[], params: URLSearchPara
850
884
  ? filtered.filter(entry => Math.floor(entry.status / 100) === Number(status[0]))
851
885
  : filtered.filter(entry => String(entry.status) === status);
852
886
  }
887
+ const helper = params.get("helper")?.trim().toLowerCase();
888
+ if (helper === "intercepted" || helper === "1" || helper === "true") {
889
+ filtered = filtered.filter(entry => Boolean(entry.shadowCallRewrittenFrom));
890
+ }
853
891
  const tailRaw = params.get("tail")?.trim();
854
892
  if (tailRaw) {
855
893
  const tail = Number.parseInt(tailRaw, 10);
@@ -939,10 +977,13 @@ export function sealRequestAttemptIdentity(
939
977
  attempt: PersistedUsageAttempt | undefined,
940
978
  provider: string,
941
979
  adapter: string,
980
+ accountLogLabel?: string,
942
981
  ): void {
943
982
  if (!attempt) return;
944
983
  attempt.provider = provider;
945
984
  attempt.adapter = adapter;
985
+ if (isCodexUsageAccountLogLabel(accountLogLabel)) attempt.accountLogLabel = accountLogLabel;
986
+ else delete attempt.accountLogLabel;
946
987
  }
947
988
 
948
989
  export function noteAttemptSend(
@@ -54,6 +54,7 @@ import {
54
54
  codexProbeQuotaScope,
55
55
  type CodexAuthContext,
56
56
  } from "../../codex/auth-context";
57
+ import { codexAuthContextLogLabel } from "../../codex/account-label";
57
58
  import {
58
59
  formatCodexProviderForLog,
59
60
  recordCodexUpstreamOutcome,
@@ -184,6 +185,10 @@ export async function handleResponsesCompact(
184
185
  return formatErrorResponse(404, "invalid_request_error", err instanceof Error ? err.message : String(err));
185
186
  }
186
187
  const selectedModelId = route.modelId;
188
+ const qualifiedRoute = route as typeof route & {
189
+ codexAccountId?: string;
190
+ codexAccountNamespace?: string;
191
+ };
187
192
  logCtx.requestedModel = raw.model;
188
193
  logCtx.model = selectedModelId;
189
194
  logCtx.provider = route.providerName;
@@ -197,7 +202,7 @@ export async function handleResponsesCompact(
197
202
  logCtx.resolvedModel = route.modelId;
198
203
  }
199
204
 
200
- if (route.codexAccountMode === "direct") {
205
+ if (route.codexAccountMode === "direct" && qualifiedRoute.codexAccountId === undefined) {
201
206
  try { validateForwardAdmissionCredential(req.headers, config); }
202
207
  catch (err) {
203
208
  if (err instanceof ForwardAdmissionCredentialError) return formatErrorResponse(401, "authentication_error", err.message);
@@ -218,7 +223,11 @@ export async function handleResponsesCompact(
218
223
  const headers = new Headers({ "content-type": "application/json" });
219
224
  try {
220
225
  if (route.codexAccountMode) {
221
- authCtx = await resolveCodexAuthContext(req.headers, config, route.codexAccountMode, { modelId: selectedModelId });
226
+ authCtx = await resolveCodexAuthContext(req.headers, config, route.codexAccountMode, {
227
+ accountId: qualifiedRoute.codexAccountId,
228
+ modelId: selectedModelId,
229
+ });
230
+ logCtx.accountLogLabel = codexAuthContextLogLabel(authCtx, config);
222
231
  const selected = headersForCodexAuthContext(req.headers, authCtx);
223
232
  compactProvider = applyCodexAuthContextToProvider(route.provider, authCtx, route.codexAccountMode);
224
233
  for (const name of FORWARD_HEADERS) {
@@ -233,7 +242,7 @@ export async function handleResponsesCompact(
233
242
  }
234
243
  } catch (err) {
235
244
  if (err instanceof CodexAccountCooldownError) {
236
- return cooldownErrorResponse(err);
245
+ return cooldownErrorResponse(err, Date.now(), qualifiedRoute.codexAccountNamespace);
237
246
  }
238
247
  if (err instanceof CodexThreadAffinityExpiredError) {
239
248
  return formatErrorResponse(409, "invalid_request_error", "Codex thread account affinity expired; start a new session");
@@ -247,7 +256,9 @@ export async function handleResponsesCompact(
247
256
  throw err;
248
257
  }
249
258
  const base = (compactProvider.baseUrl ?? "").replace(/\/$/, "");
250
- if (compactProvider.apiKey) headers.set("authorization", `Bearer ${resolveEnvValue(compactProvider.apiKey)}`);
259
+ if (compactProvider.authMode !== "forward" && compactProvider.apiKey) {
260
+ headers.set("authorization", `Bearer ${resolveEnvValue(compactProvider.apiKey)}`);
261
+ }
251
262
  const { reasoning: _reasoning, ...compactBodyRaw } = raw as typeof raw & { reasoning?: unknown };
252
263
  // The regular /v1/responses path applies sanitizeReasoningInputContent via the adapter's
253
264
  // buildRequest, but the compact endpoint forwards directly. Apply the same sanitizer here
@@ -264,6 +275,7 @@ export async function handleResponsesCompact(
264
275
  recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
265
276
  ...meta,
266
277
  threadId: compactThreadId,
278
+ fixedAccount: authCtx.fixedAccount,
267
279
  modelId: selectedModelId,
268
280
  probeLeaseId: codexProbeLeaseId(authCtx),
269
281
  probeQuotaScope: codexProbeQuotaScope(authCtx),