@bitkyc08/opencodex 2.46.0-preview.20260907 → 2.47.0-preview.20260908

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 (65) hide show
  1. package/README.md +22 -0
  2. package/SPONSORS.md +105 -0
  3. package/gui/dist/assets/index-B8ZZ0ivI.css +1 -0
  4. package/gui/dist/assets/{index-NcAVXkST.js → index-CNTWhC5F.js} +21 -21
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +2 -1
  7. package/src/adapters/cursor/protobuf-request.ts +34 -21
  8. package/src/adapters/cursor/tool-guidance.ts +2 -2
  9. package/src/adapters/cursor/tool-result-normalize.ts +22 -2
  10. package/src/adapters/exec-tool-result-normalize.ts +87 -0
  11. package/src/adapters/kiro.ts +29 -18
  12. package/src/adapters/responses-code-mode.ts +14 -4
  13. package/src/adapters/tool-catalog-nudge.ts +2 -2
  14. package/src/bridge.ts +40 -24
  15. package/src/claude/inbound.ts +21 -6
  16. package/src/claude/outbound.ts +43 -26
  17. package/src/cli/capabilities.ts +27 -0
  18. package/src/cli/integrations.ts +1 -1
  19. package/src/cli/models-runtime-subcommands.ts +2 -0
  20. package/src/cli/models-runtime.ts +108 -1
  21. package/src/cli/observe.ts +18 -3
  22. package/src/cli/usage-report.ts +6 -1
  23. package/src/clients/config-export.ts +5 -3
  24. package/src/codex/auth-api.ts +36 -0
  25. package/src/codex/catalog/provider-fetch.ts +2 -1
  26. package/src/codex/quota-auto-refresh-state.ts +8 -0
  27. package/src/codex/quota-auto-refresh.ts +157 -27
  28. package/src/codex/warmup.ts +5 -0
  29. package/src/config/provider-validation.ts +70 -1
  30. package/src/config.ts +73 -1
  31. package/src/generated/compatibility-version.json +67 -55
  32. package/src/lib/json-byte-size.ts +61 -0
  33. package/src/lib/provider-outbound.ts +11 -10
  34. package/src/oauth/index.ts +42 -8
  35. package/src/oauth/orcarouter.ts +200 -0
  36. package/src/providers/key-store.ts +22 -0
  37. package/src/providers/registry.ts +78 -29
  38. package/src/responses/citation-markers.ts +56 -24
  39. package/src/responses/reasoning-envelope.ts +53 -19
  40. package/src/server/auth-cors.ts +8 -0
  41. package/src/server/chat-completions.ts +7 -0
  42. package/src/server/chat-native.ts +65 -3
  43. package/src/server/claude-messages.ts +30 -10
  44. package/src/server/effort-policy.ts +183 -1
  45. package/src/server/management/agent-settings-routes.ts +50 -9
  46. package/src/server/management/config-routes.ts +2 -0
  47. package/src/server/management/logs-usage-routes.ts +11 -3
  48. package/src/server/management/model-routes.ts +65 -1
  49. package/src/server/management/model-rows.ts +5 -0
  50. package/src/server/management/provider-routes.ts +99 -6
  51. package/src/server/management/route-registry.ts +2 -0
  52. package/src/server/management/usage-aggregate-cache.ts +14 -7
  53. package/src/server/responses/compact.ts +3 -2
  54. package/src/server/responses/core.ts +16 -2
  55. package/src/server/startup-health-cache.ts +31 -9
  56. package/src/types/config.ts +5 -0
  57. package/src/types/provider.ts +4 -0
  58. package/src/usage/cost.ts +27 -31
  59. package/src/usage/summary.ts +52 -9
  60. package/src/usage/time-range.ts +48 -0
  61. package/src/usage/user-cost-overlays.ts +35 -3
  62. package/src/vision/anthropic-describe.ts +46 -2
  63. package/src/web-search/anthropic-executor.ts +49 -3
  64. package/src/web-search/parse.ts +1 -1
  65. package/gui/dist/assets/index-BFgUC17B.css +0 -1
@@ -695,6 +695,7 @@ export interface PiProviderBlock {
695
695
  baseUrl: string;
696
696
  api: string;
697
697
  apiKey: string;
698
+ compat?: { sendSessionAffinityHeaders: boolean };
698
699
  models: PiModelEntry[];
699
700
  }
700
701
 
@@ -816,7 +817,7 @@ export interface GajaeGeneratedConfig {
816
817
  * model. The rest of this contract (omitting `cost`) is still ours rather than
817
818
  * a claim about Pi's acceptance.
818
819
  */
819
- function buildPiClientConfig(ctx: ExportContext): PiGeneratedConfig {
820
+ function buildPiClientConfig(ctx: ExportContext, sendSessionAffinityHeaders = false): PiGeneratedConfig {
820
821
  const models: PiModelEntry[] = [];
821
822
  for (const model of normalizeExportModels(ctx.models)) {
822
823
  // Text is the one modality every routed model supports; anything richer must come
@@ -859,6 +860,7 @@ function buildPiClientConfig(ctx: ExportContext): PiGeneratedConfig {
859
860
  baseUrl: ctx.baseUrl,
860
861
  api: PI_API_DIALECT,
861
862
  apiKey: LOOPBACK_API_KEY_PLACEHOLDER,
863
+ ...(sendSessionAffinityHeaders ? { compat: { sendSessionAffinityHeaders: true } } : {}),
862
864
  models,
863
865
  },
864
866
  },
@@ -1031,7 +1033,7 @@ function buildOpencodeContribution(ctx: ExportContext): ManagedContribution {
1031
1033
  }
1032
1034
 
1033
1035
  function buildPiContribution(ctx: ExportContext): ManagedContribution {
1034
- const doc = buildPiClientConfig(ctx);
1036
+ const doc = buildPiClientConfig(ctx, true);
1035
1037
  return singleFragment("pi", ["providers", OPENCODE_PROVIDER_ID], doc.providers[OPENCODE_PROVIDER_ID]);
1036
1038
  }
1037
1039
 
@@ -1127,7 +1129,7 @@ export const EXPORT_CLIENTS: Record<ExportClientId, ExportClientSpec> = {
1127
1129
  destination: env => piConfigPath(env),
1128
1130
  apiKeyEnv: "",
1129
1131
  exportHint: "Pi reads a non-secret placeholder from models.json; loopback needs no key.",
1130
- build: buildPiClientConfig,
1132
+ build: ctx => buildPiClientConfig(ctx, true),
1131
1133
  format: "json",
1132
1134
  summarize: summarizePi,
1133
1135
  buildContribution: buildPiContribution,
@@ -1835,6 +1835,42 @@ export async function listCodexAuthAccountsSnapshot(
1835
1835
  };
1836
1836
  }
1837
1837
 
1838
+ /** One opted-in account's metadata; reuse the bounded WHAM 401 recovery and generation fence. */
1839
+ export async function refreshCodexQuotaForActivation(config: OcxConfig, accountId: string): Promise<void> {
1840
+ if (accountId === MAIN_CODEX_ACCOUNT_ID) {
1841
+ const lease = tryAcquireNativeMainProfileClaim();
1842
+ if (!lease) return;
1843
+ try {
1844
+ reconcileMainCodexAccountRuntimeState();
1845
+ if (isAccountNeedsReauth(accountId)) return;
1846
+ const identityGeneration = captureMainAccountIdentityGeneration();
1847
+ const writerGeneration = captureConfigGeneration();
1848
+ try {
1849
+ // Refresh may need an exclusive claim; prepare before WHAM takes its shared claim.
1850
+ if (!await getValidMainAccountToken({ preserveReauth: true })) return;
1851
+ } catch (error) {
1852
+ if (error instanceof MainAccountTokenRefreshError && error.reason === "reauth"
1853
+ && isMainAccountIdentityGenerationLive(identityGeneration)) {
1854
+ markAccountNeedsReauth(accountId, writerGeneration);
1855
+ }
1856
+ return;
1857
+ }
1858
+ if (isAccountNeedsReauth(accountId)) return;
1859
+ await fetchMainAccountInfoAttempt(true, 1, lease, false, false);
1860
+ } finally {
1861
+ lease.release();
1862
+ }
1863
+ return;
1864
+ }
1865
+ const account = configuredPoolAccount(config, accountId);
1866
+ if (!account) return;
1867
+ const writerGeneration = captureConfigGeneration();
1868
+ const result = await fetchPoolAccountQuota(accountId, true, account.plan);
1869
+ if (result.needsReauth && result.credentialGeneration !== undefined) {
1870
+ markAccountNeedsReauth(accountId, writerGeneration, result.credentialGeneration);
1871
+ }
1872
+ }
1873
+
1838
1874
  export async function listCodexAuthAccounts(config: OcxConfig, forceRefresh = false): Promise<CodexAuthAccountDto[]> {
1839
1875
  return (await listCodexAuthAccountsSnapshot(config, forceRefresh)).accounts;
1840
1876
  }
@@ -1335,7 +1335,8 @@ function modelInputModalities(
1335
1335
  item.input_modalities
1336
1336
  ?? item.modalities
1337
1337
  ?? metadata?.input_modalities
1338
- ?? capabilityRecord?.input_modalities,
1338
+ ?? capabilityRecord?.input_modalities
1339
+ ?? plainRecord(item.architecture)?.input_modalities,
1339
1340
  8,
1340
1341
  24,
1341
1342
  )?.filter(value => (
@@ -4,13 +4,21 @@ export type CodexQuotaAutoRefreshWindows = { fiveHour?: number; weekly?: number
4
4
 
5
5
  export const completedByAccount = new Map<string, CodexQuotaAutoRefreshWindows>();
6
6
  export const retryAfterByAccount = new Map<string, number>();
7
+ export const scheduledByAccount = new Map<string, CodexQuotaAutoRefreshWindows>();
8
+ export const quotaRefreshAfterByAccount = new Map<string, number>();
7
9
 
10
+ /** Drop every activation record when its account is removed. */
8
11
  export function forgetCodexQuotaAutoRefreshAccount(accountId: string): void {
9
12
  completedByAccount.delete(accountId);
10
13
  retryAfterByAccount.delete(accountId);
14
+ scheduledByAccount.delete(accountId);
15
+ quotaRefreshAfterByAccount.delete(accountId);
11
16
  }
12
17
 
18
+ /** Clear the dependency-free activation bookkeeping for isolated tests. */
13
19
  export function resetCodexQuotaAutoRefreshStateForTests(): void {
14
20
  completedByAccount.clear();
15
21
  retryAfterByAccount.clear();
22
+ scheduledByAccount.clear();
23
+ quotaRefreshAfterByAccount.clear();
16
24
  }
@@ -1,5 +1,5 @@
1
1
  import { mutatePersistedConfig } from "../config";
2
- import { registerStateSweepAfterTick } from "../lib/state-store-sweeper";
2
+ import { captureConfigGeneration, registerStateSweepAfterTick } from "../lib/state-store-sweeper";
3
3
  import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
4
4
  import { normalizeResetAt } from "../providers/quota-wire";
5
5
  import { providerCodexAccountMode } from "../providers/registry";
@@ -7,17 +7,20 @@ import type { OcxConfig } from "../types";
7
7
  import { isSelectableCodexPoolAccount } from "./account-id";
8
8
  import { reconcileMainCodexAccountRuntimeState } from "./account-lifecycle";
9
9
  import { isCodexAccountPaused } from "./account-pause";
10
- import { isAccountNeedsReauth } from "./account-runtime-state";
11
- import { getValidCodexToken } from "./account-store";
10
+ import { isAccountNeedsReauth, markAccountNeedsReauth } from "./account-runtime-state";
11
+ import { getValidCodexToken, isCodexAccountGenerationLive } from "./account-store";
12
+ import { codexAccountLogLabel } from "./account-label";
12
13
  import { getMainAccountToken, getValidMainAccountToken, MAIN_CODEX_ACCOUNT_ID } from "./main-account";
13
14
  import { isMainAccountHardLocked } from "./main-account-hard-lock";
14
15
  import { tryAcquireNativeMainProfileClaim } from "./native-main-admission";
15
16
  import { withNativeMainSharedClaim } from "./native-main-claim";
16
17
  import { resolveNativeProfileContext } from "./native-profile-store";
17
- import { getAccountQuota, type StoredAccountQuota } from "./quota";
18
- import { warmCodexAccount } from "./warmup";
18
+ import { getMainQuotaCredentialGeneration, observeMainQuotaCredential } from "./main-account-cache";
19
+ import { applyAccountQuotaFromUpstreamHeaders, getAccountQuota, type StoredAccountQuota } from "./quota";
20
+ import { CodexWarmupError, codexWarmupFailureReason, warmCodexAccount } from "./warmup";
19
21
  import {
20
- completedByAccount, retryAfterByAccount, resetCodexQuotaAutoRefreshStateForTests,
22
+ completedByAccount, retryAfterByAccount, scheduledByAccount, quotaRefreshAfterByAccount,
23
+ resetCodexQuotaAutoRefreshStateForTests,
21
24
  type CodexQuotaAutoRefreshWindows,
22
25
  } from "./quota-auto-refresh-state";
23
26
  export type { CodexQuotaAutoRefreshWindows } from "./quota-auto-refresh-state";
@@ -36,6 +39,7 @@ export interface CodexQuotaAutoRefreshStatus {
36
39
 
37
40
  export interface CodexQuotaAutoRefreshRunDeps {
38
41
  getQuota?: (accountId: string) => StoredAccountQuota | null;
42
+ refreshQuota?: (config: OcxConfig, accountId: string) => Promise<void>;
39
43
  /** Only false means skipped; existing void callbacks still report a successful warmup. */
40
44
  warmAccount?: (config: OcxConfig, accountId: string) => Promise<void | false>;
41
45
  persistCompleted?: (
@@ -47,6 +51,7 @@ export interface CodexQuotaAutoRefreshRunDeps {
47
51
 
48
52
  let inFlight: Promise<void> | null = null;
49
53
 
54
+ /** Report upstream window availability separately from persisted spending intent. */
50
55
  export function codexQuotaAutoRefreshStatus(
51
56
  config: OcxConfig,
52
57
  accountId: string,
@@ -62,6 +67,7 @@ export function codexQuotaAutoRefreshStatus(
62
67
  };
63
68
  }
64
69
 
70
+ /** Select retained, enabled boundaries newer than both durable and in-memory completions. */
65
71
  export function dueCodexQuotaAutoRefreshWindows(
66
72
  config: OcxConfig,
67
73
  accountId: string,
@@ -69,38 +75,110 @@ export function dueCodexQuotaAutoRefreshWindows(
69
75
  now: number,
70
76
  completed = completedByAccount.get(accountId),
71
77
  ): CodexQuotaAutoRefreshWindows | null {
72
- if (!quota) return null;
73
78
  const saved = config.codexQuotaAutoRefresh?.[accountId];
79
+ const scheduled = scheduledByAccount.get(accountId) ?? (
80
+ saved?.nextFiveHourResetAt !== undefined || saved?.nextWeeklyResetAt !== undefined
81
+ ? { fiveHour: saved.nextFiveHourResetAt, weekly: saved.nextWeeklyResetAt } : undefined
82
+ );
74
83
  const due: CodexQuotaAutoRefreshWindows = {};
75
- const shortResetAt = normalizeResetAt(quota.shortResetAt);
76
- const weeklyResetAt = normalizeResetAt(quota.weeklyResetAt);
84
+ const shortResetAt = normalizeResetAt(scheduled ? scheduled.fiveHour : quota?.shortResetAt);
85
+ const weeklyResetAt = normalizeResetAt(scheduled ? scheduled.weekly : quota?.weeklyResetAt);
77
86
  if (saved?.fiveHour === true
78
- && quota.shortWindowSeconds === FIVE_HOUR_WINDOW_SECONDS
87
+ && (scheduled?.fiveHour !== undefined || saved.nextFiveHourResetAt !== undefined
88
+ || quota?.shortWindowSeconds === FIVE_HOUR_WINDOW_SECONDS)
79
89
  && shortResetAt !== undefined
80
90
  && shortResetAt <= now
81
- && normalizeResetAt(saved.lastFiveHourResetAt) !== shortResetAt
82
- && normalizeResetAt(completed?.fiveHour) !== shortResetAt) {
91
+ && shortResetAt > (normalizeResetAt(saved.lastFiveHourResetAt) ?? -1)
92
+ && shortResetAt > (normalizeResetAt(completed?.fiveHour) ?? -1)) {
83
93
  due.fiveHour = shortResetAt;
84
94
  }
85
95
  if (saved?.weekly === true
86
96
  && weeklyResetAt !== undefined
87
97
  && weeklyResetAt <= now
88
- && normalizeResetAt(saved.lastWeeklyResetAt) !== weeklyResetAt
89
- && normalizeResetAt(completed?.weekly) !== weeklyResetAt) {
98
+ && weeklyResetAt > (normalizeResetAt(saved.lastWeeklyResetAt) ?? -1)
99
+ && weeklyResetAt > (normalizeResetAt(completed?.weekly) ?? -1)) {
90
100
  due.weekly = weeklyResetAt;
91
101
  }
92
102
  return due.fiveHour === undefined && due.weekly === undefined ? null : due;
93
103
  }
94
104
 
105
+ /** Retain the earliest uncompleted observation, including across process restarts. */
106
+ function rememberWindows(config: OcxConfig, accountId: string, quota: StoredAccountQuota | null): void {
107
+ const saved = config.codexQuotaAutoRefresh?.[accountId];
108
+ if (!saved) return;
109
+ const completed = completedByAccount.get(accountId);
110
+ const previous = scheduledByAccount.get(accountId) ?? {
111
+ fiveHour: normalizeResetAt(saved.nextFiveHourResetAt),
112
+ weekly: normalizeResetAt(saved.nextWeeklyResetAt),
113
+ };
114
+ const next: CodexQuotaAutoRefreshWindows = {};
115
+ for (const window of ["fiveHour", "weekly"] as const) {
116
+ if (!saved[window]) continue;
117
+ const done = normalizeResetAt(completed?.[window]
118
+ ?? (window === "fiveHour" ? saved.lastFiveHourResetAt : saved.lastWeeklyResetAt));
119
+ const observed = normalizeResetAt(window === "fiveHour"
120
+ ? quota?.shortWindowSeconds === FIVE_HOUR_WINDOW_SECONDS ? quota.shortResetAt : undefined
121
+ : quota?.weeklyResetAt);
122
+ const candidates = [normalizeResetAt(previous[window]), observed]
123
+ .filter((value): value is number => value !== undefined && (done === undefined || value > done));
124
+ if (candidates.length) next[window] = Math.min(...candidates);
125
+ }
126
+ scheduledByAccount.set(accountId, next);
127
+ if (normalizeResetAt(saved.nextFiveHourResetAt) === next.fiveHour
128
+ && normalizeResetAt(saved.nextWeeklyResetAt) === next.weekly) return;
129
+ try {
130
+ const outcome = mutatePersistedConfig(persisted => {
131
+ const current = persisted.codexQuotaAutoRefresh?.[accountId];
132
+ if (!current) return { changed: false, value: null };
133
+ const setting = { ...current };
134
+ // A settings change that raced this sweep remains authoritative.
135
+ delete setting.nextFiveHourResetAt;
136
+ delete setting.nextWeeklyResetAt;
137
+ if (current.fiveHour && next.fiveHour !== undefined) setting.nextFiveHourResetAt = next.fiveHour;
138
+ if (current.weekly && next.weekly !== undefined) setting.nextWeeklyResetAt = next.weekly;
139
+ persisted.codexQuotaAutoRefresh = { ...persisted.codexQuotaAutoRefresh, [accountId]: setting };
140
+ return { changed: true, value: setting };
141
+ });
142
+ if (outcome.status !== "unavailable" && outcome.value) {
143
+ config.codexQuotaAutoRefresh = { ...config.codexQuotaAutoRefresh, [accountId]: outcome.value };
144
+ }
145
+ } catch {
146
+ // Keep the in-memory deadline and retry its narrow persistence on the next tick.
147
+ }
148
+ }
149
+
150
+ /** Load metadata recovery only when an opted-in account actually needs a probe. */
151
+ async function refreshQuota(config: OcxConfig, accountId: string): Promise<void> {
152
+ const { refreshCodexQuotaForActivation } = await import("./auth-api");
153
+ await refreshCodexQuotaForActivation(config, accountId);
154
+ }
155
+
156
+ /** Keep billable main-account work behind the current pause, reauth and hard-lock policy. */
95
157
  function mainWarmupRestricted(config: OcxConfig): boolean {
96
158
  return isMainAccountHardLocked(config)
97
159
  || isCodexAccountPaused(config, MAIN_CODEX_ACCOUNT_ID)
98
160
  || isAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
99
161
  }
100
162
 
163
+ /** Warm the exact account and fence quota/reauth publication to the dispatched credential. */
101
164
  async function warmAccount(config: OcxConfig, accountId: string): Promise<void | false> {
165
+ const writerGeneration = captureConfigGeneration();
102
166
  if (accountId !== MAIN_CODEX_ACCOUNT_ID) {
103
- await warmCodexAccount(await getValidCodexToken(accountId));
167
+ const token = await getValidCodexToken(accountId);
168
+ if (isCodexAccountPaused(config, accountId) || isAccountNeedsReauth(accountId)) return false;
169
+ try {
170
+ await warmCodexAccount({ ...token, onCompleted: headers => {
171
+ if (isCodexAccountGenerationLive(accountId, token.generation)) {
172
+ applyAccountQuotaFromUpstreamHeaders(accountId, headers, writerGeneration);
173
+ }
174
+ } });
175
+ } catch (error) {
176
+ if (error instanceof CodexWarmupError && error.status === 401) {
177
+ markAccountNeedsReauth(accountId, writerGeneration, token.generation);
178
+ }
179
+ throw error;
180
+ }
181
+ if (!isCodexAccountGenerationLive(accountId, token.generation)) return false;
104
182
  return;
105
183
  }
106
184
  const lease = tryAcquireNativeMainProfileClaim();
@@ -116,13 +194,36 @@ async function warmAccount(config: OcxConfig, accountId: string): Promise<void |
116
194
  if (!token || token.accessToken !== prepared.accessToken
117
195
  || token.chatgptAccountId !== prepared.chatgptAccountId) return false;
118
196
  if (mainWarmupRestricted(config)) return false;
119
- await warmCodexAccount(token);
197
+ const writer = observeMainQuotaCredential(token.accessToken, token.chatgptAccountId);
198
+ const credentialGeneration = getMainQuotaCredentialGeneration();
199
+ const credentialStillLive = () => {
200
+ reconcileMainCodexAccountRuntimeState();
201
+ const current = getMainAccountToken();
202
+ return current?.accessToken === token.accessToken
203
+ && current.chatgptAccountId === token.chatgptAccountId
204
+ && getMainQuotaCredentialGeneration() === credentialGeneration;
205
+ };
206
+ try {
207
+ await warmCodexAccount({ ...token, onCompleted: headers => {
208
+ if (writer && credentialStillLive()) {
209
+ applyAccountQuotaFromUpstreamHeaders(accountId, headers, writerGeneration, writer);
210
+ }
211
+ } });
212
+ } catch (error) {
213
+ if (error instanceof CodexWarmupError && error.status === 401
214
+ && credentialStillLive()) {
215
+ markAccountNeedsReauth(accountId, writerGeneration);
216
+ }
217
+ throw error;
218
+ }
219
+ if (!credentialStillLive()) return false;
120
220
  });
121
221
  } finally {
122
222
  lease.release();
123
223
  }
124
224
  }
125
225
 
226
+ /** Patch completion markers without replacing concurrent account-setting changes. */
126
227
  function persistCompleted(
127
228
  config: OcxConfig,
128
229
  accountId: string,
@@ -148,6 +249,7 @@ function persistCompleted(
148
249
  }
149
250
  }
150
251
 
252
+ /** Retry failed marker persistence without sending another billable warmup. */
151
253
  function retryPendingMarkers(
152
254
  config: OcxConfig,
153
255
  persist: NonNullable<CodexQuotaAutoRefreshRunDeps["persistCompleted"]>,
@@ -163,6 +265,7 @@ function retryPendingMarkers(
163
265
  }
164
266
  }
165
267
 
268
+ /** Coalesce sweeps, refresh stale metadata and activate due accounts with bounded concurrency. */
166
269
  export async function runCodexQuotaAutoRefresh(
167
270
  config: OcxConfig,
168
271
  now = Date.now(),
@@ -175,30 +278,55 @@ export async function runCodexQuotaAutoRefresh(
175
278
  const quotaFor = deps.getQuota ?? getAccountQuota;
176
279
  const warm = deps.warmAccount ?? warmAccount;
177
280
  const persist = deps.persistCompleted ?? persistCompleted;
281
+ const refresh = deps.refreshQuota ?? refreshQuota;
178
282
  inFlight = (async () => {
179
283
  retryPendingMarkers(config, persist);
180
284
  const accountIds = [
181
285
  MAIN_CODEX_ACCOUNT_ID,
182
286
  ...(config.codexAccounts ?? []).filter(isSelectableCodexPoolAccount).map(account => account.id),
183
287
  ];
184
- const due = accountIds.flatMap(accountId => {
185
- if (isCodexAccountPaused(config, accountId)
186
- || isAccountNeedsReauth(accountId)
187
- || (accountId === MAIN_CODEX_ACCOUNT_ID && isMainAccountHardLocked(config))
188
- || (retryAfterByAccount.get(accountId) ?? 0) > now) return [];
189
- const windows = dueCodexQuotaAutoRefreshWindows(config, accountId, quotaFor(accountId), now);
190
- return windows ? [{ accountId, windows }] : [];
191
- });
192
- for (let index = 0; index < due.length; index += CONCURRENCY) {
193
- await Promise.all(due.slice(index, index + CONCURRENCY).map(async ({ accountId, windows }) => {
288
+ /** Recheck spending authorization after asynchronous metadata work. */
289
+ const eligible = (accountId: string) => {
290
+ const setting = config.codexQuotaAutoRefresh?.[accountId];
291
+ const provider = config.providers[OPENAI_CODEX_PROVIDER_ID];
292
+ return provider?.disabled !== true && isCanonicalOpenAiForwardProvider(provider)
293
+ && providerCodexAccountMode(OPENAI_CODEX_PROVIDER_ID, provider) === "pool"
294
+ && (accountId === MAIN_CODEX_ACCOUNT_ID || config.codexAccounts?.some(
295
+ account => account.id === accountId && isSelectableCodexPoolAccount(account)))
296
+ && (setting?.fiveHour === true || setting?.weekly === true)
297
+ && !isCodexAccountPaused(config, accountId) && !isAccountNeedsReauth(accountId)
298
+ && !(accountId === MAIN_CODEX_ACCOUNT_ID && isMainAccountHardLocked(config));
299
+ };
300
+ for (let index = 0; index < accountIds.length; index += CONCURRENCY) {
301
+ await Promise.all(accountIds.slice(index, index + CONCURRENCY).map(async accountId => {
302
+ if (!eligible(accountId)) return;
303
+ // Capture before WHAM can move an idle window's reset into the future.
304
+ rememberWindows(config, accountId, quotaFor(accountId));
305
+ const quota = quotaFor(accountId);
306
+ if ((!quota || now - quota.updatedAt >= RETRY_MS)
307
+ && (quotaRefreshAfterByAccount.get(accountId) ?? 0) <= now) {
308
+ quotaRefreshAfterByAccount.set(accountId, now + RETRY_MS);
309
+ try { await refresh(config, accountId); } catch { /* Retry metadata at the bounded cadence. */ }
310
+ }
311
+ if (!eligible(accountId)) return;
312
+ rememberWindows(config, accountId, quotaFor(accountId));
313
+ if ((retryAfterByAccount.get(accountId) ?? 0) > now) return;
314
+ const windows = dueCodexQuotaAutoRefreshWindows(config, accountId, quotaFor(accountId), now);
315
+ if (!windows) return;
194
316
  try {
195
317
  if (await warm(config, accountId) === false) return;
196
318
  retryAfterByAccount.delete(accountId);
197
319
  const completed = { ...completedByAccount.get(accountId), ...windows };
198
320
  completedByAccount.set(accountId, completed);
199
321
  persist(config, accountId, completed);
200
- } catch {
322
+ rememberWindows(config, accountId, quotaFor(accountId));
323
+ } catch (error) {
201
324
  retryAfterByAccount.set(accountId, now + RETRY_MS);
325
+ const account = config.codexAccounts?.find(candidate => candidate.id === accountId);
326
+ const label = account ? codexAccountLogLabel(account) : "main";
327
+ console.warn(`[codex-quota-auto-refresh] ${label}: ${codexWarmupFailureReason(error)}; ${
328
+ isAccountNeedsReauth(accountId) ? "reauthentication required" : "retry in five minutes"
329
+ }`);
202
330
  }
203
331
  }));
204
332
  }
@@ -206,6 +334,7 @@ export async function runCodexQuotaAutoRefresh(
206
334
  return inFlight;
207
335
  }
208
336
 
337
+ /** Attach activation to the shared minute sweep and return its owner-scoped cleanup. */
209
338
  export function registerCodexQuotaAutoRefreshWorker(config: OcxConfig): () => void {
210
339
  return registerStateSweepAfterTick({
211
340
  name: "codex-quota-auto-refresh",
@@ -213,6 +342,7 @@ export function registerCodexQuotaAutoRefreshWorker(config: OcxConfig): () => vo
213
342
  });
214
343
  }
215
344
 
345
+ /** Clear scheduling and single-flight state between isolated test cases. */
216
346
  export function resetCodexQuotaAutoRefreshForTests(): void {
217
347
  inFlight = null;
218
348
  resetCodexQuotaAutoRefreshStateForTests();
@@ -22,6 +22,8 @@ export interface CodexWarmupOptions {
22
22
  chatgptAccountId: string;
23
23
  model?: string;
24
24
  timeoutMs?: number;
25
+ /** Publish quota headers only after a completed inference, never on a failed stream. */
26
+ onCompleted?: (headers: Headers) => void;
25
27
  }
26
28
 
27
29
  const CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
@@ -213,6 +215,7 @@ async function drainWarmupSse(body: ReadableStream<Uint8Array>, signal: AbortSig
213
215
  }
214
216
  }
215
217
 
218
+ /** Bound one inference attempt and publish metadata only after a successful terminal event. */
216
219
  async function tryWarmup(options: CodexWarmupOptions, model: string): Promise<void> {
217
220
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
218
221
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0 || timeoutMs > MAX_TIMEOUT_MS) {
@@ -263,6 +266,8 @@ async function tryWarmup(options: CodexWarmupOptions, model: string): Promise<vo
263
266
 
264
267
  try {
265
268
  await drainWarmupSse(body, signal);
269
+ // Metadata publication must not turn completed inference into another billable retry.
270
+ try { options.onCompleted?.(res.headers); } catch { /* The caller can refresh metadata later. */ }
266
271
  } finally {
267
272
  try {
268
273
  void body.cancel().catch(() => {});
@@ -4,7 +4,7 @@ import {
4
4
  isValidModelDiscoveryModelId,
5
5
  MODEL_DISCOVERY_MAX_MODELS,
6
6
  } from "../providers/model-discovery-limits";
7
- import { modelRecordValue } from "../reasoning-effort";
7
+ import { isDeclaredReasoningEffort, modelRecordValue } from "../reasoning-effort";
8
8
  import {
9
9
  isWirePinnedModel,
10
10
  MODEL_ADAPTER_OVERRIDE_ALLOWED,
@@ -27,6 +27,75 @@ const REASONING_SUMMARY_DELIVERY_SET = new Set<string>(REASONING_SUMMARY_DELIVER
27
27
  const DISPLAY_NAME_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;
28
28
  const MAX_MODEL_DISPLAY_NAME_LENGTH = 128;
29
29
 
30
+ /** Operator pins share one strict boundary across config and management writes. */
31
+ export function pinnedReasoningEffortConfigError(value: unknown, allowClear = false): string | null {
32
+ if (value === undefined || (allowClear && (value === null || value === ""))) return null;
33
+ return typeof value === "string" && isDeclaredReasoningEffort(value)
34
+ ? null : "pinnedReasoningEffort must be a declared reasoning effort";
35
+ }
36
+
37
+ export function modelPinnedEffortsConfigError(
38
+ value: unknown,
39
+ field = "modelPinnedEfforts",
40
+ allowTombstones = false,
41
+ ): string | null {
42
+ if (value === undefined || (allowTombstones && value === null)) return null;
43
+ if (!value || typeof value !== "object" || Array.isArray(value)
44
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
45
+ return `${field} must be a plain object`;
46
+ }
47
+ const keys = new Set<string>();
48
+ for (const [key, effort] of Object.entries(value)) {
49
+ const normalized = key.trim();
50
+ if (!normalized || ["__proto__", "prototype", "constructor"].includes(normalized)) {
51
+ return `${field} keys must be nonblank model ids and must not be reserved object keys`;
52
+ }
53
+ if (keys.has(normalized)) return `${field} keys must be unique after trimming`;
54
+ keys.add(normalized);
55
+ if (allowTombstones && (effort === null || effort === "")) continue;
56
+ if (typeof effort !== "string" || !isDeclaredReasoningEffort(effort)) {
57
+ return `${field} values must be declared reasoning efforts`;
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+
63
+ /** Apply a validated map patch; null clears the field, entry tombstones remove one key. */
64
+ export function mergeModelPinnedEfforts(
65
+ current: Record<string, string> | undefined,
66
+ patch: unknown,
67
+ ): Record<string, string> | undefined {
68
+ if (patch === undefined) return current === undefined ? undefined : { ...current };
69
+ if (patch === null) return undefined;
70
+ const next = Object.fromEntries(Object.entries(current ?? {}).map(([key, value]) => [key.trim(), value]));
71
+ for (const [key, effort] of Object.entries(patch as Record<string, string | null>)) {
72
+ if (effort === null || effort === "") delete next[key.trim()];
73
+ else next[key.trim()] = effort;
74
+ }
75
+ return Object.keys(next).length ? next : undefined;
76
+ }
77
+
78
+ export function providerReasoningPinsConfigError(provider: Record<string, unknown>): string | null {
79
+ return pinnedReasoningEffortConfigError(provider.pinnedReasoningEffort)
80
+ ?? modelPinnedEffortsConfigError(provider.modelPinnedReasoningEfforts, "modelPinnedReasoningEfforts");
81
+ }
82
+
83
+ /** Validate only pin fields, including callers that bypass the whole-config schema. */
84
+ export function configReasoningPinsConfigError(value: unknown): string | null {
85
+ if (!value || typeof value !== "object") return null;
86
+ const raw = value as Record<string, unknown>;
87
+ const globalError = modelPinnedEffortsConfigError(raw.modelPinnedEfforts);
88
+ if (globalError) return globalError;
89
+ if (raw.providers && typeof raw.providers === "object") {
90
+ for (const provider of Object.values(raw.providers)) {
91
+ if (!provider || typeof provider !== "object") continue;
92
+ const error = providerReasoningPinsConfigError(provider as Record<string, unknown>);
93
+ if (error) return error;
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+
30
99
  /** Validate a provider destination without coupling DTO callers to config persistence. */
31
100
  export function providerBaseUrlConfigError(baseUrl: string): string | null {
32
101
  try {