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

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-MUpaVatk.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 +133 -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
package/src/types.ts CHANGED
@@ -606,6 +606,8 @@ export interface OcxConfig {
606
606
  * See src/lib/bun-stream-caps.ts.
607
607
  */
608
608
  streamMode?: "auto" | "legacy-tee" | "eager-relay";
609
+ /** Opt in to one identical-turn retry when a Responses completion has no text or tool call. */
610
+ emptyCompletionRetry?: boolean;
609
611
  /**
610
612
  * Custom override for the injected multi-agent guidance body (the text inside the
611
613
  * <multi_agent_mode> tags). When set, it replaces the built-in prompt on whichever
@@ -731,12 +733,18 @@ export interface OcxConfig {
731
733
  * first non-paused account (or clears the active selection when none remain).
732
734
  */
733
735
  pausedOauthAccountIds?: Record<string, string[]>;
736
+ /** Selection order per Codex account id; higher values are considered first. */
737
+ codexAccountPriorities?: Record<string, number>;
738
+ /** Account most recently selected manually; released when it drains or becomes unavailable. */
739
+ activeCodexAccountPinned?: string;
734
740
  /**
735
741
  * Public model-selector namespaces bound to one Codex account. Values are stored account ids;
736
742
  * `"@main"` selects the Codex Desktop/main auth.json account. Account display aliases
737
743
  * are intentionally separate from these selectors.
738
744
  */
739
745
  codexAccountNamespaces?: Record<string, string>;
746
+ /** Picker visibility override; existing non-empty maps remain enabled when omitted. */
747
+ codexAccountPickerEnabled?: boolean;
740
748
  /** Active pool account id for next session. undefined = main (passthrough as-is). */
741
749
  activeCodexAccountId?: string;
742
750
  /** Auto-switch threshold (0-100). Default 80. 0 = disabled. */
package/src/usage/log.ts CHANGED
@@ -2,13 +2,21 @@ import { chmodSync, closeSync, existsSync, fstatSync, mkdirSync, openSync, readF
2
2
  import { join } from "node:path";
3
3
  import { getConfigDir } from "../config";
4
4
  import { recordOwnedConfigPath } from "../lib/config-ownership";
5
+ import { CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
5
6
  import { usageDisplayTotalTokens } from "./totals";
6
7
  import type { OcxUsage } from "../types";
8
+ import { sanitizeLogMetadataString } from "../lib/redact";
7
9
 
8
10
  export type UsageStatus = "reported" | "unreported" | "unsupported" | "estimated";
11
+ export type CodexUsageAccountLogLabel = "main" | `p${string}`;
12
+
13
+ export function isCodexUsageAccountLogLabel(value: unknown): value is CodexUsageAccountLogLabel {
14
+ return value === "main" || (typeof value === "string" && CODEX_ACCOUNT_LOG_LABEL_RE.test(value));
15
+ }
9
16
 
10
17
  export type AttemptRecoveryKind =
11
18
  | "transient-5xx"
19
+ | "empty-completion"
12
20
  | "connection-reset"
13
21
  | "oauth-401"
14
22
  | "key-429"
@@ -19,6 +27,8 @@ export interface PersistedUsageAttempt {
19
27
  ordinal: number;
20
28
  provider: string;
21
29
  model: string;
30
+ /** Stable non-PII Codex Pool account identity for this serving attempt. */
31
+ accountLogLabel?: CodexUsageAccountLogLabel;
22
32
  adapter: string;
23
33
  status: number;
24
34
  durationMs: number;
@@ -27,6 +37,7 @@ export interface PersistedUsageAttempt {
27
37
  sendCount: number;
28
38
  recoveryKinds: AttemptRecoveryKind[];
29
39
  usageStatus: UsageStatus;
40
+ streamAborted?: true;
30
41
  inputTokenEstimate?: number;
31
42
  usage?: OcxUsage;
32
43
  totalTokens?: number;
@@ -43,6 +54,8 @@ export interface PersistedUsageEntry {
43
54
  timestamp: number;
44
55
  provider: string;
45
56
  model: string;
57
+ /** Stable non-PII Codex Pool account identity for the serving account. */
58
+ accountLogLabel?: CodexUsageAccountLogLabel;
46
59
  surface?: "claude" | "claude-desktop" | "grok";
47
60
  /** Matched configured key id; absent for environment/loopback admissions and
48
61
  * for every row written before attribution existed. */
@@ -54,6 +67,8 @@ export interface PersistedUsageEntry {
54
67
  conversationId?: string;
55
68
  resolvedModel?: string;
56
69
  requestedModel?: string;
70
+ /** Original helper model when shadow-call interception rewrote the request. */
71
+ shadowCallRewrittenFrom?: string;
57
72
  /** Reasoning effort / service-tier metadata for GUI Logs after restart. */
58
73
  requestedEffort?: string;
59
74
  /** Adapter-normalized tier and exact upstream parameter emitted for this request. */
@@ -243,6 +258,9 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null {
243
258
  provider: attempt.provider,
244
259
  model: attempt.model,
245
260
  adapter: attempt.adapter,
261
+ ...(isCodexUsageAccountLogLabel(attempt.accountLogLabel)
262
+ ? { accountLogLabel: attempt.accountLogLabel }
263
+ : {}),
246
264
  status: attempt.status,
247
265
  durationMs: attempt.durationMs,
248
266
  ...(isNonNegativeFiniteNumber(attempt.firstOutputMs)
@@ -251,6 +269,7 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null {
251
269
  sendCount: attempt.sendCount as number,
252
270
  recoveryKinds,
253
271
  usageStatus: attempt.usageStatus as UsageStatus,
272
+ ...(attempt.streamAborted === true ? { streamAborted: true as const } : {}),
254
273
  ...(isNonNegativeFiniteNumber(attempt.inputTokenEstimate)
255
274
  ? { inputTokenEstimate: attempt.inputTokenEstimate }
256
275
  : {}),
@@ -294,11 +313,15 @@ export function normalizeUsageEntryForTest(entry: PersistedUsageEntry): Persiste
294
313
 
295
314
  function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
296
315
  const attempts = normalizedAttempts(entry.attempts);
316
+ const shadowCallRewrittenFrom = sanitizeLogMetadataString(entry.shadowCallRewrittenFrom);
297
317
  return {
298
318
  requestId: entry.requestId,
299
319
  timestamp: entry.timestamp,
300
320
  provider: entry.provider,
301
321
  model: entry.model,
322
+ ...(isCodexUsageAccountLogLabel(entry.accountLogLabel)
323
+ ? { accountLogLabel: entry.accountLogLabel }
324
+ : {}),
302
325
  ...(isKnownUsageSurface(entry.surface) ? { surface: entry.surface } : {}),
303
326
  ...(typeof entry.apiKeyId === "string" && entry.apiKeyId.trim()
304
327
  // Deliberately NOT capped. `capMetadataString` protects free-form metadata
@@ -314,6 +337,7 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
314
337
  : {}),
315
338
  ...(entry.resolvedModel ? { resolvedModel: entry.resolvedModel } : {}),
316
339
  ...(entry.requestedModel ? { requestedModel: entry.requestedModel } : {}),
340
+ ...(shadowCallRewrittenFrom ? { shadowCallRewrittenFrom } : {}),
317
341
  ...(typeof entry.requestedEffort === "string" && entry.requestedEffort
318
342
  ? { requestedEffort: capMetadataString(entry.requestedEffort) }
319
343
  : {}),
@@ -1,8 +1,8 @@
1
1
  import { baseProviderLabel } from "../providers/label";
2
2
  import { canonicalAntigravityUsageModel } from "../providers/antigravity-models";
3
3
  import { usageDisplayTotalTokens } from "./totals";
4
- import type { PersistedUsageEntry, UsageStatus } from "./log";
5
- import { estimateComboCost, estimateRequestCost, effectiveServiceTier } from "./cost";
4
+ import { isCodexUsageAccountLogLabel, type PersistedUsageEntry, type UsageStatus } from "./log";
5
+ import { estimateAttemptCost, estimateComboCost, estimateRequestCost, effectiveServiceTier } from "./cost";
6
6
 
7
7
  export type UsageRange = "7d" | "30d" | "all";
8
8
  export type UsageSurface = "all" | "codex" | "claude" | "grok";
@@ -79,6 +79,28 @@ export interface UsageProvider {
79
79
  estimatedCostUsd?: number;
80
80
  }
81
81
 
82
+ export interface UsageAccount {
83
+ accountLogLabel: string;
84
+ ambiguous: boolean;
85
+ requests: number;
86
+ attemptCount: number;
87
+ measuredAttempts: number;
88
+ reportedAttempts: number;
89
+ estimatedAttempts: number;
90
+ unmeteredAttempts: number;
91
+ inputTokens: number;
92
+ outputTokens: number;
93
+ cacheReadInputTokens: number;
94
+ cacheCreationInputTokens: number;
95
+ reasoningOutputTokens: number;
96
+ totalTokens: number;
97
+ usageCoverageRatio: number;
98
+ estimatedCostUsd?: number;
99
+ pricedAttempts: number;
100
+ unpricedAttempts: number;
101
+ priceCoverageRatio: number;
102
+ }
103
+
82
104
  export interface UsageSummary {
83
105
  range: UsageRange;
84
106
  surface: UsageSurface;
@@ -88,6 +110,7 @@ export interface UsageSummary {
88
110
  days: UsageDay[];
89
111
  models: UsageModel[];
90
112
  providers: UsageProvider[];
113
+ accounts: UsageAccount[];
91
114
  }
92
115
 
93
116
  const DAY_MS = 86_400_000;
@@ -547,6 +570,132 @@ function buildProviders(entries: PersistedUsageEntry[], totalTokens: number): Us
547
570
  return providers.sort((a, b) => b.requests - a.requests);
548
571
  }
549
572
 
573
+ const LEGACY_AMBIGUOUS_ACCOUNT_LABEL = "legacy-ambiguous";
574
+
575
+ function legacyCodexAccountLabel(provider: string): string | null {
576
+ if (baseProviderLabel(provider) !== "openai") return null;
577
+ return provider.match(/-(main|p[a-f0-9]{6})$/)?.[1] ?? LEGACY_AMBIGUOUS_ACCOUNT_LABEL;
578
+ }
579
+
580
+ function accountLabelForAttribution(provider: string, explicit: unknown): string | null {
581
+ return isCodexUsageAccountLogLabel(explicit) ? explicit : legacyCodexAccountLabel(provider);
582
+ }
583
+
584
+ function buildAccounts(entries: PersistedUsageEntry[]): UsageAccount[] {
585
+ const byLabel = new Map<string, UsageAccount>();
586
+ const requestIds = new Map<string, Set<string>>();
587
+
588
+ const add = (input: {
589
+ requestId: string;
590
+ provider: string;
591
+ accountLogLabel?: string;
592
+ usageStatus: UsageStatus;
593
+ usage?: PersistedUsageEntry["usage"];
594
+ totalTokens?: number;
595
+ estimate: ReturnType<typeof estimateRequestCost>;
596
+ }): void => {
597
+ const label = accountLabelForAttribution(input.provider, input.accountLogLabel);
598
+ if (!label) return;
599
+ let row = byLabel.get(label);
600
+ if (!row) {
601
+ row = {
602
+ accountLogLabel: label,
603
+ ambiguous: label === LEGACY_AMBIGUOUS_ACCOUNT_LABEL,
604
+ requests: 0,
605
+ attemptCount: 0,
606
+ measuredAttempts: 0,
607
+ reportedAttempts: 0,
608
+ estimatedAttempts: 0,
609
+ unmeteredAttempts: 0,
610
+ inputTokens: 0,
611
+ outputTokens: 0,
612
+ cacheReadInputTokens: 0,
613
+ cacheCreationInputTokens: 0,
614
+ reasoningOutputTokens: 0,
615
+ totalTokens: 0,
616
+ usageCoverageRatio: 0,
617
+ pricedAttempts: 0,
618
+ unpricedAttempts: 0,
619
+ priceCoverageRatio: 0,
620
+ };
621
+ byLabel.set(label, row);
622
+ requestIds.set(label, new Set());
623
+ }
624
+ const ids = requestIds.get(label)!;
625
+ ids.add(input.requestId);
626
+ row.requests = ids.size;
627
+ row.attemptCount += 1;
628
+ const measured = input.usage !== undefined && isMeasuredStatus(input.usageStatus);
629
+ if (!measured) {
630
+ row.unmeteredAttempts += 1;
631
+ return;
632
+ }
633
+
634
+ row.measuredAttempts += 1;
635
+ if (input.usageStatus === "reported") row.reportedAttempts += 1;
636
+ else if (input.usageStatus === "estimated") row.estimatedAttempts += 1;
637
+ row.inputTokens += input.usage!.inputTokens;
638
+ row.outputTokens += input.usage!.outputTokens;
639
+ const creation = input.usage!.cacheCreationInputTokens;
640
+ const read = typeof input.usage!.cacheReadInputTokens === "number"
641
+ ? input.usage!.cacheReadInputTokens
642
+ : typeof input.usage!.cachedInputTokens === "number" && typeof creation === "number"
643
+ ? Math.max(0, input.usage!.cachedInputTokens - creation)
644
+ : input.usage!.cachedInputTokens;
645
+ if (typeof read === "number") row.cacheReadInputTokens += read;
646
+ if (typeof creation === "number") row.cacheCreationInputTokens += creation;
647
+ if (typeof input.usage!.reasoningOutputTokens === "number") {
648
+ row.reasoningOutputTokens += input.usage!.reasoningOutputTokens;
649
+ }
650
+ row.totalTokens += usageDisplayTotalTokens(input.usage, input.totalTokens) ?? 0;
651
+ if (input.estimate) {
652
+ row.pricedAttempts += 1;
653
+ row.estimatedCostUsd = (row.estimatedCostUsd ?? 0) + input.estimate.cost.total;
654
+ } else {
655
+ row.unpricedAttempts += 1;
656
+ }
657
+ };
658
+
659
+ for (const entry of entries) {
660
+ const tier = effectiveServiceTier(entry);
661
+ if (entry.attempts?.length) {
662
+ for (const attempt of entry.attempts) {
663
+ add({
664
+ requestId: entry.requestId,
665
+ provider: attempt.provider,
666
+ ...(attempt.accountLogLabel ? { accountLogLabel: attempt.accountLogLabel } : {}),
667
+ usageStatus: attempt.usageStatus,
668
+ ...(attempt.usage ? { usage: attempt.usage } : {}),
669
+ ...(attempt.totalTokens !== undefined ? { totalTokens: attempt.totalTokens } : {}),
670
+ estimate: estimateAttemptCost(attempt, undefined, tier),
671
+ });
672
+ }
673
+ continue;
674
+ }
675
+ add({
676
+ requestId: entry.requestId,
677
+ provider: entry.provider,
678
+ ...(entry.accountLogLabel ? { accountLogLabel: entry.accountLogLabel } : {}),
679
+ usageStatus: entry.usageStatus,
680
+ ...(entry.usage ? { usage: entry.usage } : {}),
681
+ ...(entry.totalTokens !== undefined ? { totalTokens: entry.totalTokens } : {}),
682
+ estimate: estimateRequestCost({
683
+ provider: entry.provider,
684
+ model: entry.model,
685
+ usage: entry.usage,
686
+ usageStatus: entry.usageStatus,
687
+ serviceTier: tier,
688
+ }),
689
+ });
690
+ }
691
+
692
+ for (const row of byLabel.values()) {
693
+ row.usageCoverageRatio = row.attemptCount === 0 ? 0 : row.measuredAttempts / row.attemptCount;
694
+ row.priceCoverageRatio = row.measuredAttempts === 0 ? 0 : row.pricedAttempts / row.measuredAttempts;
695
+ }
696
+ return [...byLabel.values()].sort((a, b) => b.totalTokens - a.totalTokens);
697
+ }
698
+
550
699
  export function summarizeUsage(
551
700
  entries: PersistedUsageEntry[],
552
701
  range: UsageRange,
@@ -581,5 +730,6 @@ export function summarizeUsage(
581
730
  days: buildDayGrid(range, since, now, filteredEntries),
582
731
  models: buildModels(filteredEntries, totals.totalTokens),
583
732
  providers: buildProviders(filteredEntries, totals.totalTokens),
733
+ accounts: buildAccounts(filteredEntries),
584
734
  };
585
735
  }