@bitkyc08/opencodex 2.52.0-preview.20260912 → 2.53.0-preview.20260913
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-BBOZWGB6.css +1 -0
- package/gui/dist/assets/index-D7ynYo2K.js +128 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/Cargo.lock +130 -0
- package/native/remote-workspace-helper/Cargo.toml +24 -0
- package/native/remote-workspace-helper/src/main.rs +49 -0
- package/native/remote-workspace-helper/src/protocol.rs +246 -0
- package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
- package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
- package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
- package/package.json +6 -1
- package/src/adapters/anthropic-image-normalize.ts +30 -2
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/base.ts +8 -2
- package/src/adapters/cursor/cursor-errors.ts +12 -0
- package/src/adapters/cursor/thread-continuity.ts +93 -0
- package/src/adapters/cursor.ts +104 -73
- package/src/adapters/devin/cloud-direct/chat.ts +312 -23
- package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
- package/src/adapters/devin/live-models.ts +70 -3
- package/src/adapters/devin.ts +281 -21
- package/src/adapters/google-wire-compiler.ts +14 -6
- package/src/adapters/google.ts +22 -8
- package/src/adapters/kiro/adapter.ts +316 -0
- package/src/adapters/kiro/conversation.ts +136 -0
- package/src/adapters/kiro/payload.ts +432 -0
- package/src/adapters/kiro/reasoning.ts +56 -0
- package/src/adapters/kiro/stream.ts +1153 -0
- package/src/adapters/kiro/usage.ts +223 -0
- package/src/adapters/kiro/wire.ts +76 -0
- package/src/adapters/kiro.ts +8 -2319
- package/src/adapters/mimo-free.ts +1 -1
- package/src/adapters/openai-chat-images.ts +101 -0
- package/src/adapters/openai-chat.ts +201 -181
- package/src/adapters/openai-responses.ts +92 -224
- package/src/adapters/registry.ts +0 -7
- package/src/adapters/run-turn-queue.ts +13 -6
- package/src/bridge.ts +14 -15
- package/src/chat/inbound.ts +29 -4
- package/src/chat/outbound.ts +145 -107
- package/src/claude/desktop-profile.ts +4 -6
- package/src/cli/account-api.ts +14 -0
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account-history.ts +60 -0
- package/src/cli/account-main.ts +80 -0
- package/src/cli/account.ts +11 -3
- package/src/cli/capabilities.ts +113 -0
- package/src/cli/catalog.ts +109 -0
- package/src/cli/dispatch.ts +9 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +2 -2
- package/src/cli/observe.ts +28 -1
- package/src/cli/opencode.ts +42 -8
- package/src/cli/provider-runtime.ts +11 -1
- package/src/cli/provider.ts +22 -2
- package/src/cli/registry.ts +21 -0
- package/src/cli/remote-workspace.ts +154 -0
- package/src/cli/status.ts +39 -7
- package/src/cli/usage-report.ts +14 -2
- package/src/client/hub-client.ts +34 -0
- package/src/client/hub-state.ts +9 -1
- package/src/codex/account-store.ts +78 -0
- package/src/codex/auth-api.ts +81 -54
- package/src/codex/auth-context.ts +45 -16
- package/src/codex/catalog/effort.ts +1 -1
- package/src/codex/catalog/metadata.ts +3 -6
- package/src/codex/catalog/native-models.ts +4 -4
- package/src/codex/catalog/parsing.ts +2 -20
- package/src/codex/catalog/provider-fetch.ts +10 -1
- package/src/codex/catalog/remote.ts +233 -0
- package/src/codex/catalog/sync.ts +403 -35
- package/src/codex/convergence.ts +1 -1
- package/src/codex/history-manifest.ts +36 -0
- package/src/codex/history-provider.ts +32 -5
- package/src/codex/inject.ts +9 -0
- package/src/codex/main-account.ts +113 -0
- package/src/codex/main-device-reauth-api.ts +89 -0
- package/src/codex/main-device-reauth.ts +217 -0
- package/src/codex/native-residue.ts +9 -2
- package/src/codex/quota-auto-refresh.ts +3 -2
- package/src/codex/quota-capacity.ts +98 -0
- package/src/codex/quota-history.ts +160 -0
- package/src/codex/quota-types.ts +8 -0
- package/src/codex/quota.ts +118 -91
- package/src/codex/refresh.ts +2 -1
- package/src/codex/routing.ts +90 -17
- package/src/codex/sync.ts +33 -4
- package/src/combos/request.ts +19 -1
- package/src/config/multi-agent-surface.ts +61 -0
- package/src/config/provider-validation.ts +176 -0
- package/src/config.ts +213 -11
- package/src/generated/compatibility-version.json +436 -168
- package/src/images/loop.ts +119 -36
- package/src/lib/admission.ts +12 -6
- package/src/lib/redact.ts +7 -0
- package/src/lib/translator-budget.ts +4 -3
- package/src/lib/windows-atomic-replace.ts +1 -0
- package/src/lib/windows-elevation.ts +1 -1
- package/src/oauth/chatgpt-device.ts +62 -5
- package/src/oauth/devin/cli-import.ts +130 -0
- package/src/oauth/devin.ts +63 -8
- package/src/oauth/index.ts +29 -14
- package/src/oauth/kiro.ts +18 -6
- package/src/oauth/login-cli.ts +9 -1
- package/src/oauth/meta-muse-device.ts +464 -0
- package/src/oauth/meta-muse.ts +123 -32
- package/src/oauth/pool-kernel.ts +9 -0
- package/src/oauth/pool-settings-capability.ts +2 -2
- package/src/oauth/store.ts +57 -0
- package/src/oauth/types.ts +31 -0
- package/src/providers/derive.ts +13 -3
- package/src/providers/devin-cli-authmode-migration.ts +57 -35
- package/src/providers/devin-provider-merge-migration.ts +240 -0
- package/src/providers/muse-key-quota.ts +117 -0
- package/src/providers/muse-subscription-usage.ts +14 -2
- package/src/providers/openai-sidecar.ts +25 -3
- package/src/providers/opencode-zen-rate-limit.ts +58 -0
- package/src/providers/provider-id-rewrite.ts +20 -5
- package/src/providers/quota-types.ts +12 -0
- package/src/providers/quota.ts +143 -102
- package/src/providers/reasoning-metadata.ts +543 -0
- package/src/providers/registry.ts +80 -49
- package/src/reasoning-effort.ts +26 -2
- package/src/remote/hub-usage.ts +32 -0
- package/src/remote-control/index.ts +192 -41
- package/src/remote-control/workspace-activation.ts +9 -0
- package/src/remote-control/workspace-agent-connection.ts +366 -0
- package/src/remote-control/workspace-claude-runtime.ts +243 -0
- package/src/remote-control/workspace-codex-runtime.ts +531 -0
- package/src/remote-control/workspace-codex-sandbox.ts +115 -0
- package/src/remote-control/workspace-command-runner.ts +748 -0
- package/src/remote-control/workspace-coordinator.ts +231 -0
- package/src/remote-control/workspace-device.ts +585 -0
- package/src/remote-control/workspace-executable.ts +43 -0
- package/src/remote-control/workspace-executor.ts +397 -0
- package/src/remote-control/workspace-hub.ts +519 -0
- package/src/remote-control/workspace-pi-runtime.ts +382 -0
- package/src/remote-control/workspace-process.ts +129 -0
- package/src/remote-control/workspace-rpc.ts +304 -0
- package/src/remote-control/workspace-runtime.ts +60 -0
- package/src/remote-control/workspace-secret-store.ts +39 -0
- package/src/remote-control/workspace-sessions.ts +799 -0
- package/src/remote-control/workspace-tool-bridge.ts +192 -0
- package/src/responses/code-mode-helper-compat.ts +22 -3
- package/src/responses/hosted-tool-policy.ts +0 -1
- package/src/responses/muse-tool-name-alias.ts +379 -0
- package/src/responses/plaintext-v2-agent-messages.ts +902 -0
- package/src/router.ts +7 -0
- package/src/routing/compatibility/behavior.ts +0 -1
- package/src/server/audio-client.ts +64 -0
- package/src/server/audio-dictation.ts +91 -0
- package/src/server/audio-live.ts +185 -0
- package/src/server/audio-transcriptions.ts +183 -0
- package/src/server/audio-upstream.ts +153 -0
- package/src/server/auth-cors.ts +61 -2
- package/src/server/chat-completions.ts +1 -1
- package/src/server/chat-native-sse.ts +92 -48
- package/src/server/chat-native.ts +37 -15
- package/src/server/hub-usage.ts +57 -0
- package/src/server/images.ts +4 -0
- package/src/server/index.ts +722 -57
- package/src/server/lifecycle.ts +5 -6
- package/src/server/live-call-bindings.ts +60 -0
- package/src/server/live.ts +12 -1
- package/src/server/management/agent-settings-routes.ts +25 -4
- package/src/server/management/api-access.ts +37 -0
- package/src/server/management/api-key-usage.ts +7 -2
- package/src/server/management/config-routes.ts +1 -18
- package/src/server/management/context.ts +15 -0
- package/src/server/management/logs-usage-routes.ts +2 -0
- package/src/server/management/oauth-account-routes.ts +39 -12
- package/src/server/management/provider-routes.ts +125 -2
- package/src/server/management/remote-workspace-routes.ts +140 -0
- package/src/server/management/route-registry.ts +15 -0
- package/src/server/management/usage-aggregate-cache.ts +14 -15
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management-api.ts +23 -0
- package/src/server/ports.ts +17 -0
- package/src/server/relay-eager.ts +4 -1
- package/src/server/relay.ts +70 -10
- package/src/server/request-decompress.ts +6 -3
- package/src/server/responses/agent-task-recovery.ts +25 -32
- package/src/server/responses/codex-auth-error.ts +11 -0
- package/src/server/responses/codex-ws-exchange.ts +52 -3
- package/src/server/responses/codex-ws-wire.ts +55 -0
- package/src/server/responses/compact.ts +9 -1
- package/src/server/responses/core.ts +337 -73
- package/src/server/responses/encrypted-payload.ts +45 -2
- package/src/server/responses/ws-upstream.ts +4 -1
- package/src/server/responses-self-named-namespace-scrub.ts +1 -3
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/search.ts +3 -0
- package/src/server/sse-payload-rewrite.ts +136 -51
- package/src/server/ws-bridge.ts +35 -1
- package/src/service/cli.ts +372 -0
- package/src/service/diagnostics.ts +340 -0
- package/src/service/guards.ts +303 -0
- package/src/service/health.ts +222 -0
- package/src/service/launchd.ts +853 -0
- package/src/service/orchestration.ts +617 -0
- package/src/service/repair.ts +334 -0
- package/src/service/state.ts +363 -0
- package/src/service/systemd.ts +229 -0
- package/src/service/windows-ops.ts +690 -0
- package/src/service/windows-scheduler.ts +769 -0
- package/src/service/windows-taskxml.ts +613 -0
- package/src/service.ts +22 -5550
- package/src/storage/cleanup/db.ts +258 -0
- package/src/storage/cleanup/execute.ts +358 -0
- package/src/storage/cleanup/paths.ts +189 -0
- package/src/storage/cleanup/pending.ts +140 -0
- package/src/storage/cleanup/preview.ts +292 -0
- package/src/storage/cleanup/reconcile.ts +347 -0
- package/src/storage/cleanup/restore.ts +932 -0
- package/src/storage/cleanup/satellite.ts +474 -0
- package/src/storage/cleanup/staging.ts +129 -0
- package/src/storage/cleanup/types.ts +98 -0
- package/src/storage/cleanup.ts +49 -3127
- package/src/types/accounts.ts +2 -0
- package/src/types/config.ts +13 -12
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +2 -0
- package/src/types/tools.ts +17 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +127 -0
- package/src/usage/log.ts +58 -1
- package/src/vision/eligibility.ts +13 -2
- package/src/web-search/loop.ts +56 -3
- package/gui/dist/assets/index-D_t6sCWs.js +0 -115
- package/gui/dist/assets/index-EdoPnm9_.css +0 -1
- package/src/adapters/devin-cli/acp.ts +0 -204
- package/src/adapters/devin-cli/adapter.ts +0 -345
- package/src/adapters/devin-cli/binary.ts +0 -69
- package/src/adapters/devin-cli/models.ts +0 -57
- package/src/oauth/devin-cli.ts +0 -149
- package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
package/src/codex/auth-api.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { CODEX_ACCOUNT_LOG_LABEL_RE } from "./account-label";
|
|
2
|
+
import { poolQuotaHistoryIdentity } from "./account-store";
|
|
3
|
+
import { estimateCodexQuotaCapacity, insufficientCodexCapacity, type CodexCapacityResult } from "./quota-capacity";
|
|
4
|
+
import { readUsageSnapshotForManagement } from "../usage/log";
|
|
5
|
+
import { capturePoolQuotaWriter } from "./account-store";
|
|
6
|
+
import type { PoolQuotaWriter } from "./quota-types";
|
|
7
|
+
import { getAccountQuotaHistory, isValidWhamHistoryObservation } from "./quota";
|
|
1
8
|
import {
|
|
2
9
|
ConfigMutationLockError,
|
|
3
10
|
loadConfig,
|
|
@@ -41,6 +48,7 @@ import {
|
|
|
41
48
|
} from "./account-priority";
|
|
42
49
|
import {
|
|
43
50
|
claimDueCodexQuotaRecoveryProbes,
|
|
51
|
+
codexQuotaScopeForModel,
|
|
44
52
|
claimManualResetCooldowns,
|
|
45
53
|
settleManualResetCooldown,
|
|
46
54
|
type ManualResetCooldownClaim,
|
|
@@ -49,6 +57,7 @@ import {
|
|
|
49
57
|
clearThreadAccountMapForAccount,
|
|
50
58
|
getEffectiveActiveCodexAccountId,
|
|
51
59
|
isEffectiveCodexAccountPinned,
|
|
60
|
+
isCodexAccountPlanExcluded,
|
|
52
61
|
reconcileCodexActiveAfterExclusion,
|
|
53
62
|
resetCodexRoutingForManualSelection,
|
|
54
63
|
settleCodexQuotaRecoveryProbe,
|
|
@@ -58,9 +67,9 @@ import {
|
|
|
58
67
|
MAX_ACCOUNT_PRIORITY,
|
|
59
68
|
MIN_ACCOUNT_PRIORITY,
|
|
60
69
|
normalizeAccountPoolStickyLimit,
|
|
61
|
-
|
|
70
|
+
normalizeCodexAccountPoolStrategy,
|
|
62
71
|
parseAccountPoolStickyLimit,
|
|
63
|
-
|
|
72
|
+
parseCodexAccountPoolStrategy,
|
|
64
73
|
parseAccountPriority,
|
|
65
74
|
} from "./pool-rotation";
|
|
66
75
|
import { checkAccountIdCollision, getMainChatgptAccountId, readCodexTokens, readCodexTokensResult } from "./auth-collision";
|
|
@@ -78,6 +87,7 @@ import {
|
|
|
78
87
|
parseUsageQuota,
|
|
79
88
|
setAccountQuotaFromParsed,
|
|
80
89
|
updateAccountQuota,
|
|
90
|
+
withoutRetiredCodexQuota,
|
|
81
91
|
type StoredAccountQuota,
|
|
82
92
|
type WhamUsageResponse,
|
|
83
93
|
} from "./quota";
|
|
@@ -242,49 +252,11 @@ function codexAccountPersistenceConflict(
|
|
|
242
252
|
: undefined;
|
|
243
253
|
}
|
|
244
254
|
|
|
245
|
-
/**
|
|
246
|
-
* The exact labels `parseUsageQuota` emits for the Codex Spark windows (quota.ts).
|
|
247
|
-
* Matching on the label rather than on "is a custom window" is load-bearing: the same array
|
|
248
|
-
* carries Cursor's First-party models / API usage, Anthropic's Fable / Opus / Sonnet,
|
|
249
|
-
* Antigravity's Gem / Cla, Kimi's subscription credits and a dozen dynamic provider meters.
|
|
250
|
-
*/
|
|
251
|
-
const CODEX_SPARK_WINDOW_LABELS = new Set([
|
|
252
|
-
"GPT-5.3-Codex-Spark 5h",
|
|
253
|
-
"GPT-5.3-Codex-Spark Weekly",
|
|
254
|
-
]);
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Drop the Spark window unless the operator asked for it (default hidden).
|
|
258
|
-
*
|
|
259
|
-
* Applied at the DTO boundary, never at parse or cache time: custom windows participate in
|
|
260
|
-
* quota-presence checks, snapshot reconciliation and capacity aggregation, so removing Spark
|
|
261
|
-
* upstream of this point would change routing state rather than display.
|
|
262
|
-
*
|
|
263
|
-
* Both GUI surfaces funnel through here — the Codex Auth rows directly, and /api/provider-quotas
|
|
264
|
-
* via listCodexAuthAccountsSnapshot — so one filter covers both. Filtering only one would leave
|
|
265
|
-
* the other still rendering the row the operator switched off.
|
|
266
|
-
*/
|
|
267
|
-
export function withSparkVisibility<T extends Omit<StoredAccountQuota, "updatedAt"> | StoredAccountQuota | null>(
|
|
268
|
-
quota: T,
|
|
269
|
-
): T {
|
|
270
|
-
if (!quota?.customWindows?.length) return quota;
|
|
271
|
-
if (loadConfig().showCodexSparkQuota === true) return quota;
|
|
272
|
-
const kept = quota.customWindows.filter(window => !CODEX_SPARK_WINDOW_LABELS.has(window.label));
|
|
273
|
-
if (kept.length === quota.customWindows.length) return quota;
|
|
274
|
-
// An empty list is dropped rather than serialized: an absent field and an empty array should
|
|
275
|
-
// not be two different ways of saying "no custom windows" on the wire.
|
|
276
|
-
const next = { ...quota } as Record<string, unknown>;
|
|
277
|
-
if (kept.length > 0) next.customWindows = kept;
|
|
278
|
-
else delete next.customWindows;
|
|
279
|
-
return next as T;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
255
|
function quotaForPlan<T extends Omit<StoredAccountQuota, "updatedAt"> | StoredAccountQuota | null>(
|
|
284
256
|
quota: T,
|
|
285
257
|
plan: unknown,
|
|
286
|
-
): T {
|
|
287
|
-
const visible =
|
|
258
|
+
): T | null {
|
|
259
|
+
const visible = withoutRetiredCodexQuota(quota);
|
|
288
260
|
if (!visible || !isThirtyDayOnlyCodexPlan(plan)) return visible;
|
|
289
261
|
const quotaWindows = visible;
|
|
290
262
|
return {
|
|
@@ -379,6 +351,7 @@ export type CodexAccountReauthReason =
|
|
|
379
351
|
| "forbidden";
|
|
380
352
|
|
|
381
353
|
function poolAccountDto(
|
|
354
|
+
config: OcxConfig,
|
|
382
355
|
account: CodexAccount,
|
|
383
356
|
quotaResult: PoolQuotaResult,
|
|
384
357
|
hasCredential: boolean,
|
|
@@ -413,6 +386,10 @@ function poolAccountDto(
|
|
|
413
386
|
quota: quota ? { ...quota } : null,
|
|
414
387
|
needsReauth: needsReauth || health.status === "reauth_required",
|
|
415
388
|
...(reauthReason !== undefined ? { reauthReason } : {}),
|
|
389
|
+
...(isCodexAccountPlanExcluded(config, account.id) ? {
|
|
390
|
+
selectionExcludedReason: "plan_excluded" as const,
|
|
391
|
+
selectionExcludedPlan: codexPlanValue(config.codexAccounts?.find(row => row.id === account.id)?.plan),
|
|
392
|
+
} : {}),
|
|
416
393
|
hasCredential,
|
|
417
394
|
...(quotaResult.quotaProbeSkipped ? { quotaProbeSkipped: true as const } : {}),
|
|
418
395
|
...oauthAccountHealthFields("codex", account.id, health),
|
|
@@ -1192,6 +1169,9 @@ export interface CodexAuthAccountDto {
|
|
|
1192
1169
|
* needs the operator; `/api/oauth/accounts` already carries the same field name.
|
|
1193
1170
|
*/
|
|
1194
1171
|
reauthReason?: CodexAccountReauthReason;
|
|
1172
|
+
/** Automatic selection policy only; explicit routes retain their usual auth checks. */
|
|
1173
|
+
selectionExcludedReason?: "plan_excluded";
|
|
1174
|
+
selectionExcludedPlan?: string;
|
|
1195
1175
|
hasCredential: boolean;
|
|
1196
1176
|
health: OAuthAccountHealth;
|
|
1197
1177
|
healthLabel: OAuthHealthLabel;
|
|
@@ -1358,6 +1338,7 @@ async function recoverPoolQuotaFrom401(ctx: {
|
|
|
1358
1338
|
|
|
1359
1339
|
const writerGeneration = captureConfigGeneration();
|
|
1360
1340
|
markQuotaProbeAttempted(ctx.quotaProbeEvidence, refreshed.generation);
|
|
1341
|
+
const poolWriter = capturePoolQuotaWriter(accountId, refreshed);
|
|
1361
1342
|
const replay = await fetch("https://chatgpt.com/backend-api/wham/usage", {
|
|
1362
1343
|
headers: {
|
|
1363
1344
|
Authorization: `Bearer ${refreshed.accessToken}`,
|
|
@@ -1376,7 +1357,7 @@ async function recoverPoolQuotaFrom401(ctx: {
|
|
|
1376
1357
|
return { quota: existing ?? null, needsReauth: false, credentialGeneration: refreshed.generation };
|
|
1377
1358
|
}
|
|
1378
1359
|
const result = await commitPoolQuotaResponse(replay, {
|
|
1379
|
-
accountId, existing, configuredPlan, generation: refreshed.generation, writerGeneration,
|
|
1360
|
+
accountId, existing, configuredPlan, generation: refreshed.generation, writerGeneration, poolWriter,
|
|
1380
1361
|
mayPublish: ctx.quotaProbeEvidence.mayPublish,
|
|
1381
1362
|
});
|
|
1382
1363
|
return result.freshCredentialGeneration === refreshed.generation ? {
|
|
@@ -1418,11 +1399,13 @@ async function commitPoolQuotaResponse(
|
|
|
1418
1399
|
configuredPlan: string | undefined;
|
|
1419
1400
|
generation: number;
|
|
1420
1401
|
writerGeneration: number;
|
|
1402
|
+
poolWriter?: PoolQuotaWriter;
|
|
1421
1403
|
mayPublish?: () => boolean;
|
|
1422
1404
|
},
|
|
1423
1405
|
): Promise<PoolQuotaResult> {
|
|
1424
1406
|
const { accountId, existing, configuredPlan, generation, writerGeneration } = ctx;
|
|
1425
1407
|
const data = (await resp.json()) as WhamUsageResponse;
|
|
1408
|
+
const observedAt = Date.now();
|
|
1426
1409
|
if (ctx.mayPublish?.() === false) {
|
|
1427
1410
|
return { quota: getAccountQuota(accountId), needsReauth: false, credentialGeneration: generation };
|
|
1428
1411
|
}
|
|
@@ -1440,7 +1423,8 @@ async function commitPoolQuotaResponse(
|
|
|
1440
1423
|
if (!isCodexAccountGenerationLive(accountId, generation)) {
|
|
1441
1424
|
return { quota: null, needsReauth: false, credentialGeneration: generation };
|
|
1442
1425
|
}
|
|
1443
|
-
setAccountQuotaFromParsed(accountId, quota, writerGeneration
|
|
1426
|
+
setAccountQuotaFromParsed(accountId, quota, writerGeneration, undefined, quota,
|
|
1427
|
+
ctx.poolWriter && isValidWhamHistoryObservation(data) ? { writer: ctx.poolWriter, observedAt, source: "wham", raw: quota } : undefined);
|
|
1444
1428
|
return {
|
|
1445
1429
|
quota: getAccountQuota(accountId),
|
|
1446
1430
|
needsReauth: false,
|
|
@@ -1464,6 +1448,7 @@ async function fetchFreshPoolAccountQuota(
|
|
|
1464
1448
|
let requestCredentialGeneration = readCodexAccountRecord(accountId)?.generation;
|
|
1465
1449
|
try {
|
|
1466
1450
|
const { accessToken, chatgptAccountId, generation } = await getValidToken(accountId);
|
|
1451
|
+
const poolWriter = capturePoolQuotaWriter(accountId, { accessToken, chatgptAccountId, generation });
|
|
1467
1452
|
requestCredentialGeneration = generation;
|
|
1468
1453
|
onCredentialGeneration?.(generation);
|
|
1469
1454
|
markQuotaProbeAttempted(quotaProbeEvidence, generation);
|
|
@@ -1494,7 +1479,7 @@ async function fetchFreshPoolAccountQuota(
|
|
|
1494
1479
|
return withQuotaProbeEvidence(recovered, quotaProbeEvidence);
|
|
1495
1480
|
}
|
|
1496
1481
|
const committed = await commitPoolQuotaResponse(resp, {
|
|
1497
|
-
accountId, existing, configuredPlan, generation, writerGeneration,
|
|
1482
|
+
accountId, existing, configuredPlan, generation, writerGeneration, poolWriter,
|
|
1498
1483
|
mayPublish: quotaProbeEvidence.mayPublish,
|
|
1499
1484
|
});
|
|
1500
1485
|
return withQuotaProbeEvidence(committed, quotaProbeEvidence);
|
|
@@ -1727,7 +1712,7 @@ export async function runCodexCooldownRecoveryProbes(config: OcxConfig, now = Da
|
|
|
1727
1712
|
try {
|
|
1728
1713
|
const result = await fetchPoolAccountQuota(claim.accountId, true, account.plan);
|
|
1729
1714
|
// Defence in depth: independent scopes are already excluded at the claim site.
|
|
1730
|
-
// Generic WHAM must never clear
|
|
1715
|
+
// Generic WHAM must never clear Reserve even if claim selection changes.
|
|
1731
1716
|
const recovered = (claim.scope === undefined || claim.scope === "shared")
|
|
1732
1717
|
&& isCompleteCodexQuotaRecoverySnapshot(result.freshQuota ?? null, result.freshPlan ?? account.plan);
|
|
1733
1718
|
settleCodexQuotaRecoveryProbe(claim, recovered, {
|
|
@@ -2006,6 +1991,7 @@ export async function listCodexAuthAccountsSnapshot(
|
|
|
2006
1991
|
const currentCredential = getCodexAccountCredential(accountId);
|
|
2007
1992
|
if (!currentCredential) {
|
|
2008
1993
|
return [poolAccountDto(
|
|
1994
|
+
runtimeConfig,
|
|
2009
1995
|
currentAccount,
|
|
2010
1996
|
{ quota: null, needsReauth: true },
|
|
2011
1997
|
false,
|
|
@@ -2026,6 +2012,7 @@ export async function listCodexAuthAccountsSnapshot(
|
|
|
2026
2012
|
? { ...currentAccount, plan: quotaResult.freshPlan }
|
|
2027
2013
|
: currentAccount;
|
|
2028
2014
|
return [poolAccountDto(
|
|
2015
|
+
runtimeConfig,
|
|
2029
2016
|
dtoAccount,
|
|
2030
2017
|
effectiveQuotaResult,
|
|
2031
2018
|
true,
|
|
@@ -2069,9 +2056,9 @@ export async function listCodexAuthAccountsSnapshot(
|
|
|
2069
2056
|
hasCredential: hasMainCredential,
|
|
2070
2057
|
needsReauth: mainNeedsReauth,
|
|
2071
2058
|
...(mainReauthReason !== undefined ? { reauthReason: mainReauthReason } : {}),
|
|
2072
|
-
quota: mainInfo.quota
|
|
2073
|
-
|
|
2074
|
-
|
|
2059
|
+
quota: mainInfo.quota
|
|
2060
|
+
? quotaForPlan(mainQuotaWithCarriedResetCredits(mainInfo.quota), mainInfo.plan)
|
|
2061
|
+
: null,
|
|
2075
2062
|
...oauthAccountHealthFields("codex", MAIN_CODEX_ACCOUNT_ID, mainHealth),
|
|
2076
2063
|
};
|
|
2077
2064
|
return {
|
|
@@ -2457,7 +2444,7 @@ export async function handleCodexAuthAPI(
|
|
|
2457
2444
|
pinnedAccountId: pinnedCodexAccountId(runtimeConfig) ?? null,
|
|
2458
2445
|
autoSwitchThreshold: runtimeConfig.autoSwitchThreshold ?? 80,
|
|
2459
2446
|
upstreamFailoverThreshold: runtimeConfig.upstreamFailoverThreshold ?? 3,
|
|
2460
|
-
accountPoolStrategy:
|
|
2447
|
+
accountPoolStrategy: normalizeCodexAccountPoolStrategy(runtimeConfig.accountPoolStrategy),
|
|
2461
2448
|
accountPoolStickyLimit: normalizeAccountPoolStickyLimit(runtimeConfig.accountPoolStickyLimit),
|
|
2462
2449
|
});
|
|
2463
2450
|
}
|
|
@@ -2488,12 +2475,12 @@ export async function handleCodexAuthAPI(
|
|
|
2488
2475
|
return jsonResponse({ error: "strategy or stickyLimit required" }, 400);
|
|
2489
2476
|
}
|
|
2490
2477
|
const runtimeConfig = getRuntimeConfig(config);
|
|
2491
|
-
let nextStrategy: NonNullable<ReturnType<typeof
|
|
2478
|
+
let nextStrategy: NonNullable<ReturnType<typeof parseCodexAccountPoolStrategy>> | undefined;
|
|
2492
2479
|
let nextSticky: NonNullable<ReturnType<typeof parseAccountPoolStickyLimit>> | undefined;
|
|
2493
2480
|
if (body.strategy !== undefined) {
|
|
2494
|
-
const parsed =
|
|
2481
|
+
const parsed = parseCodexAccountPoolStrategy(body.strategy);
|
|
2495
2482
|
if (parsed === null) {
|
|
2496
|
-
return jsonResponse({ error: 'strategy must be one of: quota, round-robin, fill-first' }, 400);
|
|
2483
|
+
return jsonResponse({ error: 'strategy must be one of: quota, round-robin, fill-first, reset-first' }, 400);
|
|
2497
2484
|
}
|
|
2498
2485
|
nextStrategy = parsed;
|
|
2499
2486
|
}
|
|
@@ -2509,7 +2496,7 @@ export async function handleCodexAuthAPI(
|
|
|
2509
2496
|
saveRuntimeConfig(config, runtimeConfig);
|
|
2510
2497
|
return jsonResponse({
|
|
2511
2498
|
ok: true,
|
|
2512
|
-
accountPoolStrategy:
|
|
2499
|
+
accountPoolStrategy: normalizeCodexAccountPoolStrategy(runtimeConfig.accountPoolStrategy),
|
|
2513
2500
|
accountPoolStickyLimit: normalizeAccountPoolStickyLimit(runtimeConfig.accountPoolStickyLimit),
|
|
2514
2501
|
});
|
|
2515
2502
|
}
|
|
@@ -2526,6 +2513,46 @@ export async function handleCodexAuthAPI(
|
|
|
2526
2513
|
return jsonResponse({ ok: true });
|
|
2527
2514
|
}
|
|
2528
2515
|
|
|
2516
|
+
if (url.pathname === "/api/codex-auth/quota/history" && req.method === "GET") {
|
|
2517
|
+
const accountId = url.searchParams.get("accountId");
|
|
2518
|
+
const rawLimit = url.searchParams.get("limit");
|
|
2519
|
+
if (url.searchParams.getAll("accountId").length !== 1 || !isValidCodexAccountId(accountId)
|
|
2520
|
+
|| url.searchParams.getAll("limit").length > 1
|
|
2521
|
+
|| [...url.searchParams.keys()].some(key => key !== "accountId" && key !== "limit")
|
|
2522
|
+
|| (rawLimit !== null && !/^(?:[1-9]|[1-9][0-9]|1[0-9]{2}|200)$/.test(rawLimit))) {
|
|
2523
|
+
return jsonResponse({ error: "A stored pool accountId and optional limit from 1 to 200 are required" }, 400);
|
|
2524
|
+
}
|
|
2525
|
+
const runtimeConfig = getRuntimeConfig(config);
|
|
2526
|
+
const account = configuredPoolAccount(runtimeConfig, accountId);
|
|
2527
|
+
if (!account) return jsonResponse({ error: "Unknown pool account" }, 404);
|
|
2528
|
+
const identity = poolQuotaHistoryIdentity(accountId);
|
|
2529
|
+
const allHistory = getAccountQuotaHistory(accountId);
|
|
2530
|
+
const limit = rawLimit === null ? 200 : Number(rawLimit);
|
|
2531
|
+
const history = { ...allHistory, observations: allHistory.observations.slice(-limit), truncated: allHistory.observations.length > limit };
|
|
2532
|
+
const label = account.logLabel;
|
|
2533
|
+
const labelStillUnique = () => {
|
|
2534
|
+
const current = getRuntimeConfig(config);
|
|
2535
|
+
return configuredPoolAccount(current, accountId)?.logLabel === label
|
|
2536
|
+
&& current.codexAccounts?.filter(row => codexAccountLogLabel(row) === label).length === 1;
|
|
2537
|
+
};
|
|
2538
|
+
let capacity: CodexCapacityResult = insufficientCodexCapacity("identity_unavailable");
|
|
2539
|
+
if (identity && identity === poolQuotaHistoryIdentity(accountId) && label && CODEX_ACCOUNT_LOG_LABEL_RE.test(label) && labelStillUnique()) {
|
|
2540
|
+
try {
|
|
2541
|
+
const usage = await readUsageSnapshotForManagement();
|
|
2542
|
+
if (poolQuotaHistoryIdentity(accountId) !== identity || !labelStillUnique()) capacity = insufficientCodexCapacity("identity_changed");
|
|
2543
|
+
else if (!usage.revision) capacity = insufficientCodexCapacity("ledger_unavailable");
|
|
2544
|
+
else if (usage.truncatedPrefixBytes > 0 || usage.entriesTruncated || usage.entriesDropped > 0) capacity = insufficientCodexCapacity("ledger_truncated");
|
|
2545
|
+
else capacity = estimateCodexQuotaCapacity(allHistory.observations, usage.entries, label,
|
|
2546
|
+
model => codexQuotaScopeForModel(model) === "shared");
|
|
2547
|
+
} catch { capacity = insufficientCodexCapacity("ledger_unavailable"); }
|
|
2548
|
+
}
|
|
2549
|
+
if (!configuredPoolAccount(getRuntimeConfig(config), accountId)) return jsonResponse({ error: "Unknown pool account" }, 404);
|
|
2550
|
+
if (identity !== poolQuotaHistoryIdentity(accountId) || (identity && label && !labelStillUnique())) {
|
|
2551
|
+
return jsonResponse({ accountId, ...getAccountQuotaHistory(accountId, limit), capacity: insufficientCodexCapacity("identity_changed") });
|
|
2552
|
+
}
|
|
2553
|
+
return jsonResponse({ accountId, ...history, capacity });
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2529
2556
|
if (url.pathname === "/api/codex-auth/quota" && req.method === "GET") {
|
|
2530
2557
|
const quotas: Record<string, unknown> = {};
|
|
2531
2558
|
for (const [id, q] of listAccountQuotas()) quotas[id] = q;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PoolQuotaWriter } from "./quota-types";
|
|
1
2
|
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
3
|
import {
|
|
3
4
|
CodexCredentialGenerationConflictError,
|
|
@@ -6,6 +7,7 @@ import {
|
|
|
6
7
|
CodexCredentialRefreshStaleError,
|
|
7
8
|
getCodexAccountCredential,
|
|
8
9
|
getValidCodexToken,
|
|
10
|
+
capturePoolQuotaWriter,
|
|
9
11
|
isCodexAccountGenerationLive,
|
|
10
12
|
readCodexAccountRecord,
|
|
11
13
|
} from "./account-store";
|
|
@@ -120,6 +122,7 @@ export type CodexAuthContext =
|
|
|
120
122
|
accountId: string;
|
|
121
123
|
writerGeneration: number;
|
|
122
124
|
generation: number;
|
|
125
|
+
poolQuotaWriter?: PoolQuotaWriter;
|
|
123
126
|
accessToken: string;
|
|
124
127
|
chatgptAccountId: string;
|
|
125
128
|
/** Bypass Pool selection and suppress quota/transient failover for an exact selector. */
|
|
@@ -201,6 +204,19 @@ export class CodexPoolAuthenticationError extends Error {
|
|
|
201
204
|
}
|
|
202
205
|
}
|
|
203
206
|
|
|
207
|
+
export type CodexModelAvailabilityReason = "unsupported" | "temporarily_unavailable";
|
|
208
|
+
|
|
209
|
+
/** A model/account compatibility failure is not a credential failure. */
|
|
210
|
+
export class CodexModelAvailabilityError extends CodexPoolAuthenticationError {
|
|
211
|
+
reason: CodexModelAvailabilityReason;
|
|
212
|
+
|
|
213
|
+
constructor(reason: CodexModelAvailabilityReason, message: string) {
|
|
214
|
+
super(message);
|
|
215
|
+
this.name = "CodexModelAvailabilityError";
|
|
216
|
+
this.reason = reason;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
204
220
|
class CodexAccountValidationPendingError extends CodexPoolAuthenticationError {
|
|
205
221
|
constructor() {
|
|
206
222
|
super("Codex account validation is pending; refresh quota after recovery to validate it");
|
|
@@ -558,7 +574,7 @@ export function cooldownErrorMessage(err: CodexAccountCooldownError, accountSele
|
|
|
558
574
|
if (err instanceof CodexMainAccountHardLockError || err instanceof CodexReserveUnavailableError) return err.message;
|
|
559
575
|
const until = new Date(err.cooldownUntil).toISOString();
|
|
560
576
|
const scopeLabels: Record<CodexQuotaScope, string> = {
|
|
561
|
-
|
|
577
|
+
shared: "shared native quota", reserve: "Reserve quota",
|
|
562
578
|
};
|
|
563
579
|
const scope = err.quotaScope ? scopeLabels[err.quotaScope] : null;
|
|
564
580
|
const selected = accountSelector
|
|
@@ -700,7 +716,10 @@ export async function resolveCodexAuthContext(
|
|
|
700
716
|
options.isDirectCallerEntitledToCodexModel ?? isDirectCallerEntitledToCodexModel
|
|
701
717
|
)(headers, options.modelId);
|
|
702
718
|
if (!entitled) {
|
|
703
|
-
throw new
|
|
719
|
+
throw new CodexModelAvailabilityError(
|
|
720
|
+
"unsupported",
|
|
721
|
+
"The selected ChatGPT account does not support this model",
|
|
722
|
+
);
|
|
704
723
|
}
|
|
705
724
|
}
|
|
706
725
|
if (callerMatchesObservedMain(headers)) assertMainAccountPolicy(policy);
|
|
@@ -734,7 +753,10 @@ export async function resolveCodexAuthContext(
|
|
|
734
753
|
options.modelId,
|
|
735
754
|
)?.has(MAIN_CODEX_ACCOUNT_ID) === true;
|
|
736
755
|
if (!entitled) {
|
|
737
|
-
throw new
|
|
756
|
+
throw new CodexModelAvailabilityError(
|
|
757
|
+
"unsupported",
|
|
758
|
+
"The selected ChatGPT account does not support this model",
|
|
759
|
+
);
|
|
738
760
|
}
|
|
739
761
|
}
|
|
740
762
|
assertMainAccountPolicy(policy);
|
|
@@ -861,11 +883,13 @@ export async function resolveCodexAuthContext(
|
|
|
861
883
|
return await resolveCallerOwnedMainContext();
|
|
862
884
|
}
|
|
863
885
|
if (fixedAccountId !== undefined) {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
886
|
+
if (modelEligibleAccountIds && !modelEligibleAccountIds.has(fixedAccountId)) {
|
|
887
|
+
throw new CodexModelAvailabilityError(
|
|
888
|
+
"unsupported",
|
|
889
|
+
"Selected Codex account does not support this model",
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
throw new CodexPoolAuthenticationError("Selected Codex account is unavailable");
|
|
869
893
|
}
|
|
870
894
|
// Recovery or a turn drain deliberately makes physical main unobservable.
|
|
871
895
|
// If no healthy pool route is available, report the temporary fence rather
|
|
@@ -881,13 +905,16 @@ export async function resolveCodexAuthContext(
|
|
|
881
905
|
&& (!modelEligibleAccountIds || modelEligibleAccountIds.has(MAIN_CODEX_ACCOUNT_ID))) {
|
|
882
906
|
assertMainAccountPolicy(policy);
|
|
883
907
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
908
|
+
if (modelEligibleAccountIds !== undefined) {
|
|
909
|
+
const unsupported = entitledAccountIds?.size === 0 && !mainModelGrantUnobserved;
|
|
910
|
+
throw new CodexModelAvailabilityError(
|
|
911
|
+
unsupported ? "unsupported" : "temporarily_unavailable",
|
|
912
|
+
unsupported
|
|
913
|
+
? "No eligible Codex account supports this model"
|
|
914
|
+
: "Codex accounts that support this model are currently unavailable",
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
throw new CodexPoolAuthenticationError();
|
|
891
918
|
}
|
|
892
919
|
accountId = selected;
|
|
893
920
|
if (accountId === MAIN_CODEX_ACCOUNT_ID) assertMainAccountPolicy(policy);
|
|
@@ -906,7 +933,8 @@ export async function resolveCodexAuthContext(
|
|
|
906
933
|
// Model entitlement is different: sending the request would spend a turn on an account whose
|
|
907
934
|
// authenticated roster already denied the model. Reassert this boundary after every selector.
|
|
908
935
|
if (modelEligibleAccountIds && !modelEligibleAccountIds.has(accountId)) {
|
|
909
|
-
throw new
|
|
936
|
+
throw new CodexModelAvailabilityError(
|
|
937
|
+
"unsupported",
|
|
910
938
|
fixedAccountId !== undefined
|
|
911
939
|
? "Selected Codex account does not support this model"
|
|
912
940
|
: "No eligible Codex account supports this model",
|
|
@@ -1030,6 +1058,7 @@ export async function resolveCodexAuthContext(
|
|
|
1030
1058
|
accountId,
|
|
1031
1059
|
writerGeneration,
|
|
1032
1060
|
generation: token.generation,
|
|
1061
|
+
poolQuotaWriter: capturePoolQuotaWriter(accountId, token),
|
|
1033
1062
|
accessToken: token.accessToken,
|
|
1034
1063
|
chatgptAccountId: token.chatgptAccountId,
|
|
1035
1064
|
...(fixedAccountId !== undefined ? { fixedAccount: true } : {}),
|
|
@@ -60,7 +60,7 @@ export function nativeEffortClamp(slug: string, effort: string | undefined): str
|
|
|
60
60
|
if (levels.length === 0) {
|
|
61
61
|
// Not snapshot-covered. gpt-5.6 natives have a REAL max rung (ensureGpt56ReasoningLevels
|
|
62
62
|
// restores it even off-snapshot) -> never clamp. Every other bare native (gpt-5.5,
|
|
63
|
-
//
|
|
63
|
+
// a retired slug a client still asks for, and future old-ladder slugs)
|
|
64
64
|
// really stops at xhigh — the ChatGPT backend error names exactly none..xhigh — so clamp
|
|
65
65
|
// the synthetic top tier.
|
|
66
66
|
return isGpt56NativeSlug(slug) ? null : "xhigh";
|
|
@@ -72,7 +72,6 @@ export {
|
|
|
72
72
|
} from "./native-models";
|
|
73
73
|
|
|
74
74
|
export const DOCUMENTED_NATIVE_OPENAI_ADDITIONS = [
|
|
75
|
-
"gpt-5.3-codex-spark",
|
|
76
75
|
"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
|
77
76
|
// The shipped pin also backfills older installed Codex catalogs that predate Astra.
|
|
78
77
|
NATIVE_GPT6_ASTRA_MODEL,
|
|
@@ -163,7 +162,6 @@ const NATIVE_GPT56_FAMILY = new Set<string>([
|
|
|
163
162
|
|
|
164
163
|
export const NATIVE_OPENAI_CONTEXT_OVERRIDES: Record<string, { contextWindow?: number; maxContextWindow?: number; maxInputTokens?: number }> = {
|
|
165
164
|
"gpt-5.5": { contextWindow: 272_000, maxContextWindow: 272_000 },
|
|
166
|
-
"gpt-5.3-codex-spark": { contextWindow: 100_000, maxContextWindow: 100_000 },
|
|
167
165
|
"gpt-5.6-sol": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_MAX_INPUT_TOKENS, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
168
166
|
"gpt-5.6-terra": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_MAX_INPUT_TOKENS, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
169
167
|
"gpt-5.6-luna": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_MAX_INPUT_TOKENS, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
@@ -378,8 +376,7 @@ export function nativeInputModalities(slug: string): string[] {
|
|
|
378
376
|
if (Array.isArray(upstream?.input_modalities) && upstream!.input_modalities!.length > 0) {
|
|
379
377
|
return [...upstream!.input_modalities as string[]];
|
|
380
378
|
}
|
|
381
|
-
//
|
|
382
|
-
// text+image capable, so default to the family baseline rather than text-only.
|
|
379
|
+
// Without a pinned row, retain the native family modality baseline.
|
|
383
380
|
return ["text", "image"];
|
|
384
381
|
}
|
|
385
382
|
|
|
@@ -393,7 +390,7 @@ export function nativeReasoningEfforts(slug: string): string[] {
|
|
|
393
390
|
// include ultra while Luna intentionally ends at max.
|
|
394
391
|
return levels.flatMap(l => typeof l.effort === "string" ? [l.effort] : []);
|
|
395
392
|
}
|
|
396
|
-
//
|
|
393
|
+
// Without a pinned row, use the standard old-ladder default.
|
|
397
394
|
return ["low", "medium", "high", "xhigh"];
|
|
398
395
|
}
|
|
399
396
|
|
|
@@ -854,7 +851,7 @@ function catalogNativeSlugs(): string[] {
|
|
|
854
851
|
}
|
|
855
852
|
|
|
856
853
|
export function listCatalogNativeSlugs(): string[] {
|
|
857
|
-
// Ensure documented additions (e.g. gpt-
|
|
854
|
+
// Ensure documented additions (e.g. gpt-6-astra) appear even when the bundled catalog
|
|
858
855
|
// predates the slug — mirrors nativeOpenAiSlugs() which already merges them for /v1/models.
|
|
859
856
|
return unique([...catalogNativeSlugs(), ...DOCUMENTED_NATIVE_OPENAI_ADDITIONS]);
|
|
860
857
|
}
|
|
@@ -155,7 +155,7 @@ export function nativeOpenAiAliasPresentation(slug: string): { displayName: stri
|
|
|
155
155
|
* Devlog: 260816_codexrs_multiagent_v2_and_history_perf/011 §4-bis.
|
|
156
156
|
*/
|
|
157
157
|
export const NATIVE_OPENAI_MODELS = [
|
|
158
|
-
"gpt-5.5",
|
|
158
|
+
"gpt-5.5",
|
|
159
159
|
"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
|
160
160
|
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
161
161
|
NATIVE_GPT6_ASTRA_MODEL,
|
|
@@ -178,7 +178,7 @@ export const SUPPORTED_NATIVE_OPENAI_SLUGS = new Set(NATIVE_OPENAI_MODELS);
|
|
|
178
178
|
* thing that makes a slug retired is upstream withdrawing it.
|
|
179
179
|
*/
|
|
180
180
|
export const RETIRED_NATIVE_OPENAI_MODELS: ReadonlySet<string> = new Set([
|
|
181
|
-
"gpt-5.4", "gpt-5.4-mini",
|
|
181
|
+
"gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark",
|
|
182
182
|
]);
|
|
183
183
|
|
|
184
184
|
/**
|
|
@@ -192,8 +192,8 @@ export const RETIRED_NATIVE_OPENAI_MODELS: ReadonlySet<string> = new Set([
|
|
|
192
192
|
* flipped false — letting a drain silently rewrite the operator's configured subagent model.
|
|
193
193
|
*
|
|
194
194
|
* It is an explicit list rather than `SUPPORTED_NATIVE_OPENAI_SLUGS`, which would have widened
|
|
195
|
-
* the sentinel to `gpt-5.5`
|
|
196
|
-
*
|
|
195
|
+
* the sentinel to `gpt-5.5` as well. That
|
|
196
|
+
* model was never covered, and widening would turn "fell back and answered" into a
|
|
197
197
|
* maintenance error for the most commonly configured fallback slug in the repo. Membership is
|
|
198
198
|
* the set the drain behaviour was actually reasoned about: the account-gated natives plus the
|
|
199
199
|
* flagships that just left that set.
|
|
@@ -337,15 +337,6 @@ export function findSupportedNativeTemplate(catalog: RawCatalog | null): RawEntr
|
|
|
337
337
|
) ?? null;
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
/**
|
|
341
|
-
* Native OpenAI slugs that do NOT support the Fast (priority) service tier.
|
|
342
|
-
* Upstream may advertise service_tiers for these models, but the tier is not
|
|
343
|
-
* actually available — strip it so the Codex UI does not offer a dead toggle.
|
|
344
|
-
*/
|
|
345
|
-
const NO_FAST_TIER_NATIVE_SLUGS = new Set([
|
|
346
|
-
"gpt-5.3-codex-spark",
|
|
347
|
-
]);
|
|
348
|
-
|
|
349
340
|
/** Does this row already carry an `ultrafast` tier the operator put there themselves? */
|
|
350
341
|
/**
|
|
351
342
|
* Read the opt-in from the live config, ONCE per catalog build.
|
|
@@ -438,14 +429,6 @@ export function normalizeServiceTiers(entry: RawEntry): RawEntry {
|
|
|
438
429
|
? { ...tier, description: "2x speed, increased usage" } : tier,
|
|
439
430
|
);
|
|
440
431
|
}
|
|
441
|
-
// Strip service tiers for models that do not actually support the Fast tier.
|
|
442
|
-
if (typeof entry.slug === "string" && NO_FAST_TIER_NATIVE_SLUGS.has(entry.slug)) {
|
|
443
|
-
delete entry.service_tier;
|
|
444
|
-
delete entry.service_tiers;
|
|
445
|
-
delete entry.default_service_tier;
|
|
446
|
-
delete entry.additional_speed_tiers;
|
|
447
|
-
return entry;
|
|
448
|
-
}
|
|
449
432
|
// Codex stores the user-facing config spelling as "fast", but the catalog/request
|
|
450
433
|
// service tier id is "priority" in current codex-rs. Keep legacy catalogs working.
|
|
451
434
|
if (entry.service_tier === "fast") entry.service_tier = "priority";
|
|
@@ -517,9 +500,8 @@ export function applyNativeOpenAiContextOverride(entry: RawEntry, limits?: Nativ
|
|
|
517
500
|
}
|
|
518
501
|
}
|
|
519
502
|
// providerContextCaps.openai is a ceiling for native OpenAI rows regardless of where the
|
|
520
|
-
// advertised window came from (#1430): preserved rows without a hardcoded override
|
|
521
|
-
//
|
|
522
|
-
// window.
|
|
503
|
+
// advertised window came from (#1430): preserved rows without a hardcoded override
|
|
504
|
+
// must stay under the cap too, and auto-compaction follows the capped window.
|
|
523
505
|
// The per-model window narrows the same rows for the same reason.
|
|
524
506
|
const currentContext = typeof entry.context_window === "number" ? entry.context_window : undefined;
|
|
525
507
|
const cappedContext = narrowNativeMaxContextWindow(nativeSlug, currentContext, limits);
|
|
@@ -600,6 +600,7 @@ function providerCatalogFingerprint(name: string, prov: OcxProviderConfig): Reco
|
|
|
600
600
|
maxOut: prov.modelMaxOutputTokens ?? null,
|
|
601
601
|
autoCompact: prov.modelAutoCompactTokenLimits ?? null,
|
|
602
602
|
inMod: prov.modelInputModalities ?? null,
|
|
603
|
+
capabilities: prov.modelCapabilities ?? null,
|
|
603
604
|
re: prov.modelReasoningEfforts ?? null,
|
|
604
605
|
defRe: prov.modelDefaultReasoningEfforts ?? null,
|
|
605
606
|
rsSum: prov.modelSupportsReasoningSummaries ?? null,
|
|
@@ -673,7 +674,9 @@ export function configuredContextWindow(prov: OcxProviderConfig, id: string): nu
|
|
|
673
674
|
}
|
|
674
675
|
|
|
675
676
|
export function configuredInputModalities(prov: OcxProviderConfig, id: string): string[] | undefined {
|
|
676
|
-
const
|
|
677
|
+
const declared = Object.hasOwn(prov.modelCapabilities ?? {}, id)
|
|
678
|
+
? prov.modelCapabilities?.[id]?.inputModalities : undefined;
|
|
679
|
+
const modalities = declared ?? modelRecordValue(prov.modelInputModalities, id);
|
|
677
680
|
return Array.isArray(modalities) && modalities.length > 0 ? [...modalities] : undefined;
|
|
678
681
|
}
|
|
679
682
|
|
|
@@ -1736,6 +1739,12 @@ async function fetchProviderModelsWithAuth(
|
|
|
1736
1739
|
id,
|
|
1737
1740
|
provider: name,
|
|
1738
1741
|
...(liveWindow ? { contextWindow: liveWindow } : {}),
|
|
1742
|
+
// The account catalog names the effort variants each base model has, so
|
|
1743
|
+
// its ladder is measured rather than assumed. Without this the entry
|
|
1744
|
+
// inherits the generic routed ladder and offers rungs the model rounds
|
|
1745
|
+
// away, and every client that keys an effort control off this field —
|
|
1746
|
+
// the Pi-shaped exports — renders no control at all.
|
|
1747
|
+
...(liveResult.efforts[id]?.length ? { reasoningEfforts: liveResult.efforts[id] } : {}),
|
|
1739
1748
|
...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias),
|
|
1740
1749
|
} as CatalogModel;
|
|
1741
1750
|
});
|