@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.
- package/gui/dist/assets/index-MUpaVatk.js +67 -0
- package/gui/dist/index.html +1 -1
- package/package.json +3 -3
- package/packages/ocx-notch/README.md +2 -1
- package/src/adapters/cursor/discovery.ts +6 -2
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/google-antigravity-replay.ts +24 -0
- package/src/adapters/google.ts +16 -11
- package/src/chat/inbound.ts +5 -11
- package/src/cli/account-api.ts +9 -1
- package/src/cli/account-extended.ts +4 -1
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +12 -1
- package/src/codex/account-namespaces.ts +21 -0
- package/src/codex/account-priority.ts +49 -0
- package/src/codex/account-store.ts +2 -1
- package/src/codex/auth-api.ts +108 -17
- package/src/codex/auth-context.ts +61 -16
- package/src/codex/catalog/metadata.ts +34 -12
- package/src/codex/catalog/parsing.ts +8 -1
- package/src/codex/catalog/provider-fetch.ts +24 -6
- package/src/codex/catalog.ts +1 -1
- package/src/codex/pool-rotation.ts +51 -4
- package/src/codex/quota.ts +154 -35
- package/src/codex/routing.ts +133 -33
- package/src/codex/warmup.ts +193 -85
- package/src/config.ts +84 -1
- package/src/lib/bounded-body.ts +13 -6
- package/src/lib/bun-stream-caps.ts +5 -6
- package/src/lib/redact.ts +13 -0
- package/src/oauth/index.ts +79 -12
- package/src/oauth/log.ts +3 -1
- package/src/oauth/store.ts +31 -8
- package/src/providers/antigravity-models.ts +53 -24
- package/src/providers/codex-capacity.ts +303 -0
- package/src/providers/model-rename-migration.ts +147 -0
- package/src/providers/model-rename-startup.ts +29 -0
- package/src/providers/quota.ts +126 -16
- package/src/providers/registry.ts +258 -38
- package/src/responses/parser.ts +19 -12
- package/src/responses/spill-store.ts +14 -1
- package/src/responses/state.ts +108 -14
- package/src/server/index.ts +9 -1
- package/src/server/management/logs-usage-routes.ts +1 -0
- package/src/server/management/oauth-account-routes.ts +8 -1
- package/src/server/relay.ts +10 -42
- package/src/server/request-log.ts +42 -1
- package/src/server/responses/compact.ts +16 -4
- package/src/server/responses/core.ts +217 -59
- package/src/server/responses/empty-completion-guard.ts +275 -0
- package/src/server/responses/encrypted-payload.ts +54 -39
- package/src/server/responses/fetch-helpers.ts +24 -3
- package/src/server/responses/ws-upstream.ts +318 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/ws-bridge.ts +17 -11
- package/src/types.ts +8 -0
- package/src/usage/log.ts +24 -0
- package/src/usage/summary.ts +152 -2
- package/vendor/ocx-notch/win32-x64/ocx-notch.exe +0 -0
- package/gui/dist/assets/index-BucjyD4I.js +0 -67
package/src/codex/auth-api.ts
CHANGED
|
@@ -17,19 +17,36 @@ import {
|
|
|
17
17
|
TokenRefreshError,
|
|
18
18
|
} from "./account-store";
|
|
19
19
|
import { deleteCodexAccount, reconcileMainCodexAccountRuntimeState } from "./account-lifecycle";
|
|
20
|
+
import {
|
|
21
|
+
appendDefaultCodexAccountNamespace,
|
|
22
|
+
initializeDefaultCodexAccountNamespaces,
|
|
23
|
+
} from "./account-namespaces";
|
|
20
24
|
import { isCodexAccountPaused, setCodexAccountPaused } from "./account-pause";
|
|
25
|
+
import {
|
|
26
|
+
clearCodexAccountPin,
|
|
27
|
+
getCodexAccountPriority,
|
|
28
|
+
isCodexAccountPriorityKey,
|
|
29
|
+
pinnedCodexAccountId,
|
|
30
|
+
setCodexAccountPin,
|
|
31
|
+
setCodexAccountPriority,
|
|
32
|
+
} from "./account-priority";
|
|
21
33
|
import {
|
|
22
34
|
clearCodexAccountCooldown,
|
|
23
35
|
clearThreadAccountMapForAccount,
|
|
24
36
|
getEffectiveActiveCodexAccountId,
|
|
37
|
+
isEffectiveCodexAccountPinned,
|
|
25
38
|
reconcileCodexActiveAfterExclusion,
|
|
26
39
|
resetCodexRoutingForManualSelection,
|
|
27
40
|
} from "./routing";
|
|
28
41
|
import {
|
|
42
|
+
DEFAULT_ACCOUNT_PRIORITY,
|
|
43
|
+
MAX_ACCOUNT_PRIORITY,
|
|
44
|
+
MIN_ACCOUNT_PRIORITY,
|
|
29
45
|
normalizeAccountPoolStickyLimit,
|
|
30
46
|
normalizeAccountPoolStrategy,
|
|
31
47
|
parseAccountPoolStickyLimit,
|
|
32
48
|
parseAccountPoolStrategy,
|
|
49
|
+
parseAccountPriority,
|
|
33
50
|
} from "./pool-rotation";
|
|
34
51
|
import { checkAccountIdCollision, getMainChatgptAccountId, readCodexTokens, readCodexTokensResult } from "./auth-collision";
|
|
35
52
|
export { checkAccountIdCollision, getMainChatgptAccountId } from "./auth-collision";
|
|
@@ -38,6 +55,7 @@ import { clearAccountNeedsReauth, isAccountNeedsReauth, markAccountNeedsReauth }
|
|
|
38
55
|
import {
|
|
39
56
|
clearAccountQuota,
|
|
40
57
|
getAccountQuota,
|
|
58
|
+
isCodexFiveHourQuotaPlan,
|
|
41
59
|
isCodexQuotaExhausted,
|
|
42
60
|
listAccountQuotas,
|
|
43
61
|
parseUsageQuota,
|
|
@@ -66,7 +84,7 @@ import {
|
|
|
66
84
|
} from "./main-account-cache";
|
|
67
85
|
export { clearMainAccountInfoCache } from "./main-account-cache";
|
|
68
86
|
import { maskEmail } from "../lib/privacy";
|
|
69
|
-
import {
|
|
87
|
+
import { codexWarmupFailureReason, isCodexWarmupQuotaFailure, warmCodexAccount } from "./warmup";
|
|
70
88
|
export { maskEmail } from "../lib/privacy";
|
|
71
89
|
import type { CodexAccount, OcxConfig } from "../types";
|
|
72
90
|
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
|
|
@@ -169,6 +187,7 @@ function poolAccountDto(
|
|
|
169
187
|
quotaResult: PoolQuotaResult,
|
|
170
188
|
hasCredential: boolean,
|
|
171
189
|
paused: boolean,
|
|
190
|
+
priority: number,
|
|
172
191
|
): CodexAuthAccountDto {
|
|
173
192
|
const quota = quotaForPlan(quotaResult.quota, account.plan);
|
|
174
193
|
const needsReauth = !hasCredential || quotaResult.needsReauth || isAccountNeedsReauth(account.id);
|
|
@@ -181,6 +200,7 @@ function poolAccountDto(
|
|
|
181
200
|
...(account.logLabel !== undefined ? { logLabel: account.logLabel } : {}),
|
|
182
201
|
isMain: false,
|
|
183
202
|
paused,
|
|
203
|
+
priority,
|
|
184
204
|
quota: quota ? { ...quota } : null,
|
|
185
205
|
needsReauth,
|
|
186
206
|
hasCredential,
|
|
@@ -258,14 +278,11 @@ async function verifyCodexAccountWarmup(
|
|
|
258
278
|
return { ok: true, validatedAt: Date.now() };
|
|
259
279
|
} catch (err) {
|
|
260
280
|
const reason = codexWarmupFailureReason(err);
|
|
261
|
-
const upstream = err instanceof CodexWarmupError ? err.upstreamDetail : undefined;
|
|
262
281
|
return {
|
|
263
282
|
ok: false,
|
|
264
283
|
error: err,
|
|
265
284
|
response: jsonResponse({
|
|
266
|
-
error:
|
|
267
|
-
? `Codex account warmup failed: ${upstream}`
|
|
268
|
-
: "Codex account warmup failed. Reauthenticate the account and try again.",
|
|
285
|
+
error: "Codex account warmup failed. Reauthenticate the account and try again.",
|
|
269
286
|
code: "codex_warmup_failed",
|
|
270
287
|
reason,
|
|
271
288
|
accountId,
|
|
@@ -312,6 +329,9 @@ function exhaustedQuotaRetryAt(
|
|
|
312
329
|
): number | undefined {
|
|
313
330
|
const monthlyOnly = isThirtyDayOnlyPlan(plan);
|
|
314
331
|
const resetSeconds: number[] = [];
|
|
332
|
+
if (!monthlyOnly && isCodexFiveHourQuotaPlan(plan)
|
|
333
|
+
&& quota.fiveHourPercent !== undefined && quota.fiveHourPercent >= 100
|
|
334
|
+
&& quota.fiveHourResetAt !== undefined) resetSeconds.push(quota.fiveHourResetAt);
|
|
315
335
|
if (!monthlyOnly && quota.weeklyPercent !== undefined && quota.weeklyPercent >= 100
|
|
316
336
|
&& quota.weeklyResetAt !== undefined) resetSeconds.push(quota.weeklyResetAt);
|
|
317
337
|
if (quota.monthlyPercent !== undefined && quota.monthlyPercent >= 100
|
|
@@ -402,16 +422,23 @@ function commitCodexAccountConfig(
|
|
|
402
422
|
|
|
403
423
|
const accounts = persisted.codexAccounts ?? [];
|
|
404
424
|
const existingIdx = accounts.findIndex(candidate => candidate.id === account.id);
|
|
425
|
+
let committedAccount: CodexAccount;
|
|
405
426
|
if (existingIdx >= 0) {
|
|
406
|
-
|
|
427
|
+
committedAccount = withCodexAccountLogLabel({
|
|
407
428
|
...accounts[existingIdx],
|
|
408
429
|
...account,
|
|
409
430
|
isMain: false,
|
|
410
431
|
}, accounts);
|
|
432
|
+
accounts[existingIdx] = committedAccount;
|
|
411
433
|
} else {
|
|
412
|
-
|
|
434
|
+
committedAccount = withCodexAccountLogLabel({ ...account, isMain: false }, accounts);
|
|
435
|
+
accounts.push(committedAccount);
|
|
413
436
|
}
|
|
414
437
|
persisted.codexAccounts = accounts;
|
|
438
|
+
if (mode === "create" && persisted.codexAccountPickerEnabled !== undefined) {
|
|
439
|
+
initializeDefaultCodexAccountNamespaces(persisted);
|
|
440
|
+
appendDefaultCodexAccountNamespace(persisted, committedAccount);
|
|
441
|
+
}
|
|
415
442
|
if (quotaPause === "add") setCodexAccountPaused(persisted, account.id, true);
|
|
416
443
|
else if (quotaPause === "release") setCodexAccountPaused(persisted, account.id, false);
|
|
417
444
|
return { changed: true, value: { ok: true } as CodexAccountConfigCommit };
|
|
@@ -485,7 +512,7 @@ interface MainAccountInfoFetchResult {
|
|
|
485
512
|
}
|
|
486
513
|
|
|
487
514
|
export async function fetchMainAccountInfo(forceRefresh = false): Promise<MainAccountInfo> {
|
|
488
|
-
const { info } = await fetchMainAccountInfoAttempt(forceRefresh, 1);
|
|
515
|
+
const { info } = await fetchMainAccountInfoAttempt(forceRefresh, 1, forceRefresh);
|
|
489
516
|
return info;
|
|
490
517
|
}
|
|
491
518
|
|
|
@@ -494,16 +521,21 @@ const EMPTY_MAIN_ACCOUNT_INFO: MainAccountInfo = { email: null, plan: null, quot
|
|
|
494
521
|
async function retryMainAccountInfoIfIdentityChanged(
|
|
495
522
|
requestAccountId: string | null,
|
|
496
523
|
retriesRemaining: number,
|
|
524
|
+
explicitRefresh: boolean,
|
|
497
525
|
): Promise<MainAccountInfoFetchResult | null> {
|
|
498
526
|
const currentAccountId = getMainChatgptAccountId();
|
|
499
527
|
if (currentAccountId === null || currentAccountId === requestAccountId) return null;
|
|
500
528
|
reconcileMainCodexAccountRuntimeState();
|
|
501
529
|
return retriesRemaining > 0
|
|
502
|
-
? fetchMainAccountInfoAttempt(true, retriesRemaining - 1)
|
|
530
|
+
? fetchMainAccountInfoAttempt(true, retriesRemaining - 1, explicitRefresh)
|
|
503
531
|
: { info: EMPTY_MAIN_ACCOUNT_INFO };
|
|
504
532
|
}
|
|
505
533
|
|
|
506
|
-
async function fetchMainAccountInfoAttempt(
|
|
534
|
+
async function fetchMainAccountInfoAttempt(
|
|
535
|
+
forceRefresh: boolean,
|
|
536
|
+
retriesRemaining: number,
|
|
537
|
+
explicitRefresh = forceRefresh,
|
|
538
|
+
): Promise<MainAccountInfoFetchResult> {
|
|
507
539
|
const writerGeneration = captureConfigGeneration();
|
|
508
540
|
reconcileMainCodexAccountRuntimeState();
|
|
509
541
|
const tokenRead = readCodexTokensResult();
|
|
@@ -530,7 +562,7 @@ async function fetchMainAccountInfoAttempt(forceRefresh: boolean, retriesRemaini
|
|
|
530
562
|
});
|
|
531
563
|
if (!resp.ok) {
|
|
532
564
|
const terminalAuthFailure = await isTerminalMainAuthResponse(resp);
|
|
533
|
-
const retried = await retryMainAccountInfoIfIdentityChanged(requestAccountId, retriesRemaining);
|
|
565
|
+
const retried = await retryMainAccountInfoIfIdentityChanged(requestAccountId, retriesRemaining, explicitRefresh);
|
|
534
566
|
if (retried) return retried;
|
|
535
567
|
if (terminalAuthFailure) {
|
|
536
568
|
clearMainAccountInfoCache();
|
|
@@ -539,7 +571,7 @@ async function fetchMainAccountInfoAttempt(forceRefresh: boolean, retriesRemaini
|
|
|
539
571
|
return { info: EMPTY_MAIN_ACCOUNT_INFO };
|
|
540
572
|
}
|
|
541
573
|
const data = (await resp.json()) as WhamUsageResponse;
|
|
542
|
-
const retried = await retryMainAccountInfoIfIdentityChanged(requestAccountId, retriesRemaining);
|
|
574
|
+
const retried = await retryMainAccountInfoIfIdentityChanged(requestAccountId, retriesRemaining, explicitRefresh);
|
|
543
575
|
if (retried) return retried;
|
|
544
576
|
const plan = nonEmptyPlan(data.plan_type) ?? nonEmptyPlan(cached?.plan) ?? nonEmptyPlan(getMainAccountPlan());
|
|
545
577
|
const quota = parseUsageQuota({ ...data, ...(plan ? { plan_type: plan } : {}) });
|
|
@@ -551,7 +583,9 @@ async function fetchMainAccountInfoAttempt(forceRefresh: boolean, retriesRemaini
|
|
|
551
583
|
ts: Date.now(),
|
|
552
584
|
};
|
|
553
585
|
setMainAccountInfoCache(result);
|
|
554
|
-
|
|
586
|
+
// A successful quota probe only proves /wham/usage accepted the token. Only an
|
|
587
|
+
// operator-requested refresh may retract a quarantine raised by Responses traffic.
|
|
588
|
+
if (explicitRefresh) clearAccountNeedsReauth(MAIN_CODEX_ACCOUNT_ID);
|
|
555
589
|
// Mirror main quota + plan into the shared stores so the rotation engine can
|
|
556
590
|
// score and auto-switch the main account exactly like a pool account (Option A).
|
|
557
591
|
setMainAccountPlan(result.plan);
|
|
@@ -564,7 +598,7 @@ async function fetchMainAccountInfoAttempt(forceRefresh: boolean, retriesRemaini
|
|
|
564
598
|
...(freshResetCredits !== undefined ? { freshResetCredits } : {}),
|
|
565
599
|
};
|
|
566
600
|
} catch {
|
|
567
|
-
const retried = await retryMainAccountInfoIfIdentityChanged(requestAccountId, retriesRemaining);
|
|
601
|
+
const retried = await retryMainAccountInfoIfIdentityChanged(requestAccountId, retriesRemaining, explicitRefresh);
|
|
568
602
|
return retried ?? { info: EMPTY_MAIN_ACCOUNT_INFO };
|
|
569
603
|
}
|
|
570
604
|
}
|
|
@@ -635,6 +669,8 @@ export interface CodexAuthAccountDto {
|
|
|
635
669
|
logLabel?: string;
|
|
636
670
|
isMain: boolean;
|
|
637
671
|
paused: boolean;
|
|
672
|
+
/** Selection order; higher is considered first. */
|
|
673
|
+
priority: number;
|
|
638
674
|
quota: (StoredAccountQuota | (Omit<StoredAccountQuota, "updatedAt"> & { updatedAt: number })) | null;
|
|
639
675
|
needsReauth?: boolean;
|
|
640
676
|
hasCredential: boolean;
|
|
@@ -871,6 +907,10 @@ export function clearCodexQuotaPrimeState(): void {
|
|
|
871
907
|
primeInFlight = null;
|
|
872
908
|
}
|
|
873
909
|
|
|
910
|
+
export function effectiveCodexAuthAccountId(config: OcxConfig): string {
|
|
911
|
+
return getEffectiveActiveCodexAccountId(config) ?? MAIN_CODEX_ACCOUNT_ID;
|
|
912
|
+
}
|
|
913
|
+
|
|
874
914
|
export async function listCodexAuthAccounts(config: OcxConfig, forceRefresh = false): Promise<CodexAuthAccountDto[]> {
|
|
875
915
|
const runtimeConfig = getRuntimeConfig(config);
|
|
876
916
|
const poolAccounts = (runtimeConfig.codexAccounts ?? []).filter(isSelectableCodexPoolAccount);
|
|
@@ -917,6 +957,7 @@ export async function listCodexAuthAccounts(config: OcxConfig, forceRefresh = fa
|
|
|
917
957
|
{ quota: null, needsReauth: true },
|
|
918
958
|
false,
|
|
919
959
|
isCodexAccountPaused(runtimeConfig, accountId),
|
|
960
|
+
getCodexAccountPriority(runtimeConfig, accountId),
|
|
920
961
|
)];
|
|
921
962
|
}
|
|
922
963
|
const resultGeneration = quotaResult.credentialGeneration ?? quotaResult.freshCredentialGeneration;
|
|
@@ -935,6 +976,7 @@ export async function listCodexAuthAccounts(config: OcxConfig, forceRefresh = fa
|
|
|
935
976
|
effectiveQuotaResult,
|
|
936
977
|
true,
|
|
937
978
|
isCodexAccountPaused(runtimeConfig, accountId),
|
|
979
|
+
getCodexAccountPriority(runtimeConfig, accountId),
|
|
938
980
|
)];
|
|
939
981
|
});
|
|
940
982
|
const hasMainCredential = readCodexTokens() !== null;
|
|
@@ -949,9 +991,15 @@ export async function listCodexAuthAccounts(config: OcxConfig, forceRefresh = fa
|
|
|
949
991
|
plan: mainInfo.plan,
|
|
950
992
|
isMain: true,
|
|
951
993
|
paused: isCodexAccountPaused(runtimeConfig, MAIN_CODEX_ACCOUNT_ID),
|
|
994
|
+
priority: getCodexAccountPriority(runtimeConfig, MAIN_CODEX_ACCOUNT_ID),
|
|
952
995
|
hasCredential: hasMainCredential,
|
|
953
996
|
needsReauth: mainNeedsReauth,
|
|
954
|
-
quota: mainInfo.quota ? {
|
|
997
|
+
quota: mainInfo.quota ? {
|
|
998
|
+
...quotaForPlan({
|
|
999
|
+
...mainInfo.quota,
|
|
1000
|
+
updatedAt: getAccountQuota(MAIN_CODEX_ACCOUNT_ID)?.updatedAt ?? Date.now(),
|
|
1001
|
+
}, mainInfo.plan),
|
|
1002
|
+
} : null,
|
|
955
1003
|
...oauthAccountHealthFields("codex", MAIN_CODEX_ACCOUNT_ID, mainHealth),
|
|
956
1004
|
};
|
|
957
1005
|
return [main, ...withQuota];
|
|
@@ -1174,6 +1222,42 @@ export async function handleCodexAuthAPI(
|
|
|
1174
1222
|
});
|
|
1175
1223
|
}
|
|
1176
1224
|
|
|
1225
|
+
if (url.pathname === "/api/codex-auth/accounts/priority" && req.method === "PUT") {
|
|
1226
|
+
let parsedBody: unknown;
|
|
1227
|
+
try { parsedBody = await req.json(); } catch { return jsonResponse({ error: "Invalid JSON" }, 400); }
|
|
1228
|
+
if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody)) {
|
|
1229
|
+
return jsonResponse({ error: "body must be an object" }, 400);
|
|
1230
|
+
}
|
|
1231
|
+
const body = parsedBody as { id?: unknown; priority?: unknown };
|
|
1232
|
+
const id = typeof body.id === "string" ? body.id.trim() : "";
|
|
1233
|
+
if (!isCodexAccountPriorityKey(id)) return jsonResponse({ error: "Invalid account id format" }, 400);
|
|
1234
|
+
|
|
1235
|
+
let priority = DEFAULT_ACCOUNT_PRIORITY;
|
|
1236
|
+
if (body.priority !== null) {
|
|
1237
|
+
const parsed = parseAccountPriority(body.priority);
|
|
1238
|
+
if (parsed === null) {
|
|
1239
|
+
return jsonResponse({
|
|
1240
|
+
error: `priority must be null or an integer ${MIN_ACCOUNT_PRIORITY}-${MAX_ACCOUNT_PRIORITY}`,
|
|
1241
|
+
}, 400);
|
|
1242
|
+
}
|
|
1243
|
+
priority = parsed;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const runtimeConfig = getRuntimeConfig(config);
|
|
1247
|
+
const exists = id === MAIN_CODEX_ACCOUNT_ID
|
|
1248
|
+
|| (runtimeConfig.codexAccounts ?? []).some(account => isSelectableCodexPoolAccount(account) && account.id === id);
|
|
1249
|
+
if (!exists) return jsonResponse({ error: "Account not found" }, 404);
|
|
1250
|
+
setCodexAccountPriority(runtimeConfig, id, priority);
|
|
1251
|
+
clearCodexAccountPin(runtimeConfig);
|
|
1252
|
+
saveRuntimeConfig(config, runtimeConfig);
|
|
1253
|
+
return jsonResponse({
|
|
1254
|
+
ok: true,
|
|
1255
|
+
id,
|
|
1256
|
+
priority,
|
|
1257
|
+
activeCodexAccountId: getEffectiveActiveCodexAccountId(runtimeConfig) ?? null,
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1177
1261
|
if (url.pathname === "/api/codex-auth/accounts/pause-exhausted" && req.method === "PUT") {
|
|
1178
1262
|
const runtimeConfig = getRuntimeConfig(config);
|
|
1179
1263
|
const result = await pauseExhaustedCodexAccounts(runtimeConfig);
|
|
@@ -1234,6 +1318,8 @@ export async function handleCodexAuthAPI(
|
|
|
1234
1318
|
if (!exists) return jsonResponse({ error: "Account not found" }, 400);
|
|
1235
1319
|
}
|
|
1236
1320
|
runtimeConfig.activeCodexAccountId = body.accountId ?? undefined;
|
|
1321
|
+
if (body.accountId == null) clearCodexAccountPin(runtimeConfig);
|
|
1322
|
+
else setCodexAccountPin(runtimeConfig, targetAccountId);
|
|
1237
1323
|
resetCodexRoutingForManualSelection(targetAccountId);
|
|
1238
1324
|
saveRuntimeConfig(config, runtimeConfig);
|
|
1239
1325
|
return jsonResponse({ ok: true, activeCodexAccountId: body.accountId, appliesImmediately: true });
|
|
@@ -1243,6 +1329,8 @@ export async function handleCodexAuthAPI(
|
|
|
1243
1329
|
const runtimeConfig = getRuntimeConfig(config);
|
|
1244
1330
|
return jsonResponse({
|
|
1245
1331
|
activeCodexAccountId: getEffectiveActiveCodexAccountId(runtimeConfig) ?? null,
|
|
1332
|
+
pinned: isEffectiveCodexAccountPinned(runtimeConfig),
|
|
1333
|
+
pinnedAccountId: pinnedCodexAccountId(runtimeConfig) ?? null,
|
|
1246
1334
|
autoSwitchThreshold: runtimeConfig.autoSwitchThreshold ?? 80,
|
|
1247
1335
|
upstreamFailoverThreshold: runtimeConfig.upstreamFailoverThreshold ?? 3,
|
|
1248
1336
|
accountPoolStrategy: normalizeAccountPoolStrategy(runtimeConfig.accountPoolStrategy),
|
|
@@ -1534,8 +1622,7 @@ export async function handleCodexAuthAPI(
|
|
|
1534
1622
|
|
|
1535
1623
|
const warmup = await verifyCodexAccountWarmup(accountId, cred.access, oauthAccountId);
|
|
1536
1624
|
const freshRegistrationQuota = quota ? { quota, ...(plan ? { plan } : {}) } : null;
|
|
1537
|
-
const quotaDeferred = !
|
|
1538
|
-
&& !warmup.ok
|
|
1625
|
+
const quotaDeferred = !warmup.ok
|
|
1539
1626
|
&& mayDeferQuotaWarmup(warmup, freshRegistrationQuota);
|
|
1540
1627
|
if (!warmup.ok && !quotaDeferred) {
|
|
1541
1628
|
const body = await warmup.response.json().catch(() => ({})) as { error?: string; reason?: string };
|
|
@@ -1569,6 +1656,9 @@ export async function handleCodexAuthAPI(
|
|
|
1569
1656
|
|
|
1570
1657
|
const releaseOwnedQuotaPause = reauth
|
|
1571
1658
|
&& readCodexAccountRecord(accountId)?.codexQuotaPauseOwned === true;
|
|
1659
|
+
const quotaPauseOwned = !reauth
|
|
1660
|
+
|| releaseOwnedQuotaPause
|
|
1661
|
+
|| !isCodexAccountPaused(latestConfig, accountId);
|
|
1572
1662
|
const credentialGeneration = saveCodexAccountCredential(accountId, {
|
|
1573
1663
|
accessToken: cred.access,
|
|
1574
1664
|
refreshToken: cred.refresh,
|
|
@@ -1605,6 +1695,7 @@ export async function handleCodexAuthAPI(
|
|
|
1605
1695
|
accountId,
|
|
1606
1696
|
codexWarmupFailureReason(warmup.error),
|
|
1607
1697
|
exhaustedQuotaRetryAt(freshRegistrationQuota.quota, freshRegistrationQuota.plan),
|
|
1698
|
+
quotaPauseOwned,
|
|
1608
1699
|
);
|
|
1609
1700
|
} else if (warmup.ok) {
|
|
1610
1701
|
markCodexAccountValidated(accountId, warmup.validatedAt, credentialGeneration);
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
isCodexAccountGenerationLive,
|
|
8
8
|
} from "./account-store";
|
|
9
9
|
import { ConfigMutationLockError } from "../config";
|
|
10
|
-
import { markAccountNeedsReauth } from "./account-runtime-state";
|
|
10
|
+
import { isAccountNeedsReauth, markAccountNeedsReauth } from "./account-runtime-state";
|
|
11
|
+
import { isCodexAccountPaused } from "./account-pause";
|
|
11
12
|
import { isCodexAccountUsable } from "./account-usability";
|
|
12
13
|
import { reconcileMainCodexAccountRuntimeState } from "./account-lifecycle";
|
|
13
14
|
import { MAIN_CODEX_ACCOUNT_ID, getMainAccountToken } from "./main-account";
|
|
@@ -38,6 +39,8 @@ export type CodexAuthContext =
|
|
|
38
39
|
generation: number;
|
|
39
40
|
accessToken: string;
|
|
40
41
|
chatgptAccountId: string;
|
|
42
|
+
/** Exact account selector: suppresses Pool rotation and failover. */
|
|
43
|
+
fixedAccount?: boolean;
|
|
41
44
|
/**
|
|
42
45
|
* Set when this request was admitted through an active quota cooldown as
|
|
43
46
|
* the account's single probe. Must be echoed into the upstream outcome so
|
|
@@ -57,6 +60,8 @@ export type CodexAuthContext =
|
|
|
57
60
|
writerGeneration: number;
|
|
58
61
|
accessToken: string;
|
|
59
62
|
chatgptAccountId: string;
|
|
63
|
+
/** Exact account selector: suppresses Pool rotation and failover. */
|
|
64
|
+
fixedAccount?: boolean;
|
|
60
65
|
/** See `pool.probeLeaseId`. */
|
|
61
66
|
probeLeaseId?: string;
|
|
62
67
|
quotaScope?: CodexQuotaScope;
|
|
@@ -100,8 +105,8 @@ export class CodexAuthContextError extends Error {
|
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
export class CodexPoolAuthenticationError extends Error {
|
|
103
|
-
constructor() {
|
|
104
|
-
super(
|
|
108
|
+
constructor(message = "OpenAI account pool has no usable account credential") {
|
|
109
|
+
super(message);
|
|
105
110
|
this.name = "CodexPoolAuthenticationError";
|
|
106
111
|
}
|
|
107
112
|
}
|
|
@@ -154,22 +159,31 @@ export function cooldownAccountLabel(accountId: string): string {
|
|
|
154
159
|
* as HTTP. The bare "cooling down" string left users with no route but commenting out the
|
|
155
160
|
* injected `openai_base_url` in config.toml.
|
|
156
161
|
*/
|
|
157
|
-
export function cooldownErrorMessage(err: CodexAccountCooldownError): string {
|
|
162
|
+
export function cooldownErrorMessage(err: CodexAccountCooldownError, accountSelector?: string): string {
|
|
158
163
|
const until = new Date(err.cooldownUntil).toISOString();
|
|
159
164
|
const scope = err.quotaScope === "spark"
|
|
160
165
|
? "Spark quota"
|
|
161
166
|
: err.quotaScope === "shared"
|
|
162
167
|
? "shared native quota"
|
|
163
168
|
: null;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
169
|
+
const selected = accountSelector
|
|
170
|
+
? `Selected Codex account selector (${accountSelector})`
|
|
171
|
+
: `Selected Codex account (${cooldownAccountLabel(err.accountId)})`;
|
|
172
|
+
const recovery = accountSelector
|
|
173
|
+
? " This request is pinned to that selector and will not switch accounts; choose another account-qualified model or retry later."
|
|
174
|
+
: " Run 'ocx account list openai' to find the id, then"
|
|
175
|
+
+ " 'ocx account clear-cooldown openai <id>' to lift it, or switch accounts with 'ocx account use openai <id>'.";
|
|
176
|
+
return `${selected}${scope ? ` ${scope} is` : " is"} cooling down until ${until}`
|
|
177
|
+
+ ` (source: ${err.cooldownSource ?? "default"}).${recovery}`;
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
/** HTTP form of {@link cooldownErrorMessage}, carrying Retry-After for well-behaved clients. */
|
|
171
|
-
export function cooldownErrorResponse(
|
|
172
|
-
|
|
181
|
+
export function cooldownErrorResponse(
|
|
182
|
+
err: CodexAccountCooldownError,
|
|
183
|
+
now = Date.now(),
|
|
184
|
+
accountSelector?: string,
|
|
185
|
+
): Response {
|
|
186
|
+
const res = formatErrorResponse(429, "rate_limit_error", cooldownErrorMessage(err, accountSelector));
|
|
173
187
|
const headers = new Headers(res.headers);
|
|
174
188
|
headers.set("Retry-After", String(Math.max(1, Math.ceil((err.cooldownUntil - now) / 1000))));
|
|
175
189
|
return new Response(res.body, { status: res.status, headers });
|
|
@@ -195,6 +209,8 @@ export function shouldMarkAccountNeedsReauthForCodexAuthFailure(cause: unknown):
|
|
|
195
209
|
|
|
196
210
|
export interface ResolveCodexAuthContextOptions {
|
|
197
211
|
excludeAccountId?: string;
|
|
212
|
+
/** Resolve exactly this account without consulting or mutating Pool selection. */
|
|
213
|
+
accountId?: string;
|
|
198
214
|
/** Final native model selected for this request, used to select its quota group. */
|
|
199
215
|
modelId?: string;
|
|
200
216
|
}
|
|
@@ -206,14 +222,20 @@ export async function resolveCodexAuthContext(
|
|
|
206
222
|
options: ResolveCodexAuthContextOptions = {},
|
|
207
223
|
): Promise<CodexAuthContext> {
|
|
208
224
|
const writerGeneration = captureConfigGeneration();
|
|
209
|
-
|
|
225
|
+
const fixedAccountId = options.accountId;
|
|
226
|
+
if (fixedAccountId !== undefined && options.excludeAccountId !== undefined) {
|
|
227
|
+
throw new Error("Codex auth context cannot select and exclude an account simultaneously");
|
|
228
|
+
}
|
|
229
|
+
if (mode === "direct" && fixedAccountId === undefined) {
|
|
210
230
|
if (!hasCallerCodexBearer(headers)) throw new CodexDirectAuthenticationError();
|
|
211
231
|
return { kind: "main", accountId: null };
|
|
212
232
|
}
|
|
213
233
|
reconcileMainCodexAccountRuntimeState();
|
|
214
234
|
const threadId = headers.get("x-codex-parent-thread-id");
|
|
215
235
|
const quotaScope = codexQuotaScopeForModel(options.modelId);
|
|
216
|
-
const resolution =
|
|
236
|
+
const resolution = fixedAccountId !== undefined
|
|
237
|
+
? { status: "selected" as const, accountId: fixedAccountId }
|
|
238
|
+
: options.excludeAccountId
|
|
217
239
|
? (() => {
|
|
218
240
|
const accountId = pickAlternateCodexAccount(config, options.excludeAccountId!, Date.now(), quotaScope);
|
|
219
241
|
return accountId
|
|
@@ -223,13 +245,28 @@ export async function resolveCodexAuthContext(
|
|
|
223
245
|
: resolveCodexAccountForThreadDetailed(threadId, config, Date.now(), quotaScope);
|
|
224
246
|
if (resolution.status === "expired") throw new CodexThreadAffinityExpiredError(resolution.accountId);
|
|
225
247
|
let accountId = resolution.status === "selected" ? resolution.accountId : null;
|
|
226
|
-
if (!accountId)
|
|
248
|
+
if (!accountId) {
|
|
249
|
+
throw new CodexPoolAuthenticationError(
|
|
250
|
+
fixedAccountId !== undefined ? "Selected Codex account is unavailable" : undefined,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
if (fixedAccountId !== undefined) {
|
|
254
|
+
if (isCodexAccountPaused(config, accountId)) {
|
|
255
|
+
throw new CodexPoolAuthenticationError("Selected Codex account is unavailable");
|
|
256
|
+
}
|
|
257
|
+
if (isAccountNeedsReauth(accountId)) {
|
|
258
|
+
throw new CodexPoolAuthenticationError("Selected Codex account needs reauthentication");
|
|
259
|
+
}
|
|
260
|
+
if (!isCodexAccountUsable(config, accountId)) {
|
|
261
|
+
throw new CodexPoolAuthenticationError("Selected Codex account is unavailable");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
227
264
|
// Lazy prime: if the selected account has no quota yet, the pool is likely
|
|
228
265
|
// unprimed (dashboard never opened, or startup prime was blocked). Kick a
|
|
229
266
|
// best-effort prime so the NEXT routing decision has real scores. This never
|
|
230
267
|
// blocks the current request, and the helper's single-flight guard collapses
|
|
231
268
|
// repeated triggers into one pass.
|
|
232
|
-
if (!getAccountQuota(accountId)) {
|
|
269
|
+
if (fixedAccountId === undefined && !getAccountQuota(accountId)) {
|
|
233
270
|
import("./auth-api")
|
|
234
271
|
.then(({ primeCodexPoolQuotas }) => primeCodexPoolQuotas(config, "pre-route"))
|
|
235
272
|
.catch(() => {});
|
|
@@ -244,6 +281,9 @@ export async function resolveCodexAuthContext(
|
|
|
244
281
|
let probeLeaseId: string | undefined;
|
|
245
282
|
let probeQuotaScope: CodexQuotaScope | undefined;
|
|
246
283
|
if (cooldownUntil) {
|
|
284
|
+
if (fixedAccountId !== undefined) {
|
|
285
|
+
throw new CodexAccountCooldownError(accountId, cooldownUntil, cooldown?.cooldownSource, cooldown?.quotaScope);
|
|
286
|
+
}
|
|
247
287
|
probeQuotaScope = cooldown?.quotaScope;
|
|
248
288
|
probeLeaseId = probeQuotaScope
|
|
249
289
|
? tryAcquireCodexQuotaScopeProbeLease(accountId, probeQuotaScope) ?? undefined
|
|
@@ -260,7 +300,9 @@ export async function resolveCodexAuthContext(
|
|
|
260
300
|
// Nothing will reach upstream, so give the probe back instead of burning it.
|
|
261
301
|
if (probeLeaseId && probeQuotaScope) releaseCodexQuotaScopeProbeLease(accountId, probeQuotaScope, probeLeaseId);
|
|
262
302
|
else if (probeLeaseId) releaseCodexQuotaProbeLease(accountId, probeLeaseId);
|
|
263
|
-
throw new CodexPoolAuthenticationError(
|
|
303
|
+
throw new CodexPoolAuthenticationError(
|
|
304
|
+
fixedAccountId !== undefined ? "Selected Codex account is unavailable" : undefined,
|
|
305
|
+
);
|
|
264
306
|
}
|
|
265
307
|
return {
|
|
266
308
|
kind: "main-pool",
|
|
@@ -268,6 +310,7 @@ export async function resolveCodexAuthContext(
|
|
|
268
310
|
writerGeneration,
|
|
269
311
|
accessToken: token.accessToken,
|
|
270
312
|
chatgptAccountId: token.chatgptAccountId,
|
|
313
|
+
...(fixedAccountId !== undefined ? { fixedAccount: true } : {}),
|
|
271
314
|
...(quotaScope ? { quotaScope } : {}),
|
|
272
315
|
...(probeLeaseId ? { probeLeaseId } : {}),
|
|
273
316
|
...(probeQuotaScope ? { probeQuotaScope } : {}),
|
|
@@ -283,6 +326,7 @@ export async function resolveCodexAuthContext(
|
|
|
283
326
|
generation: token.generation,
|
|
284
327
|
accessToken: token.accessToken,
|
|
285
328
|
chatgptAccountId: token.chatgptAccountId,
|
|
329
|
+
...(fixedAccountId !== undefined ? { fixedAccount: true } : {}),
|
|
286
330
|
...(quotaScope ? { quotaScope } : {}),
|
|
287
331
|
...(probeLeaseId ? { probeLeaseId } : {}),
|
|
288
332
|
...(probeQuotaScope ? { probeQuotaScope } : {}),
|
|
@@ -312,7 +356,8 @@ export function applyCodexAuthContextToProvider(
|
|
|
312
356
|
ctx: CodexAuthContext,
|
|
313
357
|
mode: CodexAccountMode | undefined,
|
|
314
358
|
): OcxRuntimeProviderConfig {
|
|
315
|
-
if (
|
|
359
|
+
if ((ctx.kind !== "pool" && ctx.kind !== "main-pool") || provider.authMode !== "forward") return provider;
|
|
360
|
+
if (mode !== "pool" && ctx.fixedAccount !== true) return provider;
|
|
316
361
|
return {
|
|
317
362
|
...provider,
|
|
318
363
|
_codexAccountOverride: {
|
|
@@ -35,14 +35,18 @@ import { filterSupportedNativeSlugs } from "./parsing";
|
|
|
35
35
|
import type { RawEntry } from "./parsing";
|
|
36
36
|
import { readCurrentCatalogOrCache, unique } from "./bundled";
|
|
37
37
|
|
|
38
|
+
export const NATIVE_DAYBREAK_BLUE_MODEL = "gpt-daybreak-blue-latest";
|
|
39
|
+
|
|
38
40
|
export const NATIVE_OPENAI_MODELS = [
|
|
39
41
|
"gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark",
|
|
40
42
|
"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
|
43
|
+
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
41
44
|
];
|
|
42
45
|
|
|
43
46
|
export const DOCUMENTED_NATIVE_OPENAI_ADDITIONS = [
|
|
44
47
|
"gpt-5.3-codex-spark",
|
|
45
48
|
"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
|
49
|
+
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
46
50
|
];
|
|
47
51
|
|
|
48
52
|
export const SUPPORTED_NATIVE_OPENAI_SLUGS = new Set(NATIVE_OPENAI_MODELS);
|
|
@@ -53,15 +57,18 @@ export function isUnsupportedOpenAiNativeSlug(slug: string): boolean {
|
|
|
53
57
|
return /^(?:gpt|codex)-/.test(slug);
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
/** Measured native family contract: 922k input plus up to 128k output. */
|
|
61
|
+
export const NATIVE_GPT56_CONTEXT_WINDOW = 1_050_000;
|
|
62
|
+
export const NATIVE_GPT56_MAX_INPUT_TOKENS = 922_000;
|
|
57
63
|
|
|
58
|
-
export const NATIVE_OPENAI_CONTEXT_OVERRIDES: Record<string, { contextWindow?: number; maxContextWindow?: number }> = {
|
|
64
|
+
export const NATIVE_OPENAI_CONTEXT_OVERRIDES: Record<string, { contextWindow?: number; maxContextWindow?: number; maxInputTokens?: number }> = {
|
|
59
65
|
"gpt-5.5": { contextWindow: 272_000, maxContextWindow: 272_000 },
|
|
60
66
|
"gpt-5.4": { contextWindow: 1_000_000, maxContextWindow: 1_000_000 },
|
|
61
67
|
"gpt-5.3-codex-spark": { contextWindow: 100_000, maxContextWindow: 100_000 },
|
|
62
|
-
"gpt-5.6-sol": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW },
|
|
63
|
-
"gpt-5.6-terra": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW },
|
|
64
|
-
"gpt-5.6-luna": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW },
|
|
68
|
+
"gpt-5.6-sol": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
69
|
+
"gpt-5.6-terra": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
70
|
+
"gpt-5.6-luna": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
71
|
+
[NATIVE_DAYBREAK_BLUE_MODEL]: { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
65
72
|
};
|
|
66
73
|
|
|
67
74
|
export function nativeOpenAiContextWindow(slug: string): number | undefined {
|
|
@@ -71,6 +78,10 @@ export function nativeOpenAiContextWindow(slug: string): number | undefined {
|
|
|
71
78
|
: undefined);
|
|
72
79
|
}
|
|
73
80
|
|
|
81
|
+
export function nativeOpenAiMaxInputTokens(slug: string): number | undefined {
|
|
82
|
+
return NATIVE_OPENAI_CONTEXT_OVERRIDES[slug]?.maxInputTokens;
|
|
83
|
+
}
|
|
84
|
+
|
|
74
85
|
export function nativeInputModalities(slug: string): string[] {
|
|
75
86
|
const upstream = UPSTREAM_NATIVE_ENTRIES.get(slug);
|
|
76
87
|
if (Array.isArray(upstream?.input_modalities) && upstream!.input_modalities!.length > 0) {
|
|
@@ -144,13 +155,24 @@ export function applyNativeVisibility(entries: RawEntry[], disabledNative: Set<s
|
|
|
144
155
|
return entries;
|
|
145
156
|
}
|
|
146
157
|
|
|
147
|
-
export const UPSTREAM_NATIVE_ENTRIES: Map<string, RawEntry> =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
)
|
|
158
|
+
export const UPSTREAM_NATIVE_ENTRIES: Map<string, RawEntry> = (() => {
|
|
159
|
+
const entries = new Map(
|
|
160
|
+
((upstreamModelsSnapshot as unknown as { models?: RawEntry[] }).models ?? [])
|
|
161
|
+
.filter(m => typeof m.slug === "string"
|
|
162
|
+
&& SUPPORTED_NATIVE_OPENAI_SLUGS.has(m.slug as string)
|
|
163
|
+
&& (m.slug as string).startsWith("gpt-5.6-"))
|
|
164
|
+
.map(m => [m.slug as string, m] as const),
|
|
165
|
+
);
|
|
166
|
+
const sol = entries.get("gpt-5.6-sol");
|
|
167
|
+
if (sol) {
|
|
168
|
+
entries.set(NATIVE_DAYBREAK_BLUE_MODEL, {
|
|
169
|
+
...JSON.parse(JSON.stringify(sol)) as RawEntry,
|
|
170
|
+
slug: NATIVE_DAYBREAK_BLUE_MODEL,
|
|
171
|
+
display_name: "GPT Daybreak Blue",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return entries;
|
|
175
|
+
})();
|
|
154
176
|
|
|
155
177
|
export function upstreamNativeEntry(slug: string): RawEntry | null {
|
|
156
178
|
const entry = UPSTREAM_NATIVE_ENTRIES.get(slug);
|
|
@@ -243,13 +243,20 @@ export function isNativeOpenAiEntry(entry: RawEntry): boolean {
|
|
|
243
243
|
return typeof entry.slug === "string" && !entry.slug.includes("/");
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
function nativeAutoCompactLimit(contextWindow: number, maxInputTokens?: number): number {
|
|
247
|
+
const ninetyPercent = Math.floor(contextWindow * 0.9);
|
|
248
|
+
return typeof maxInputTokens === "number" && maxInputTokens > 0
|
|
249
|
+
? Math.min(ninetyPercent, maxInputTokens, contextWindow)
|
|
250
|
+
: ninetyPercent;
|
|
251
|
+
}
|
|
252
|
+
|
|
246
253
|
export function applyNativeOpenAiContextOverride(entry: RawEntry): void {
|
|
247
254
|
if (!isNativeOpenAiEntry(entry)) return;
|
|
248
255
|
const override = NATIVE_OPENAI_CONTEXT_OVERRIDES[entry.slug as string];
|
|
249
256
|
if (!override) return;
|
|
250
257
|
if (typeof override.contextWindow === "number") {
|
|
251
258
|
entry.context_window = override.contextWindow;
|
|
252
|
-
entry.auto_compact_token_limit =
|
|
259
|
+
entry.auto_compact_token_limit = nativeAutoCompactLimit(override.contextWindow, override.maxInputTokens);
|
|
253
260
|
}
|
|
254
261
|
if (typeof override.maxContextWindow === "number") {
|
|
255
262
|
entry.max_context_window = override.maxContextWindow;
|