@coseung2/opencodex 2.8.0-cs.12 → 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 +3 -2
- 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 +73 -10
- package/src/codex/auth-api.ts +312 -45
- 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 +204 -82
- 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/oauth/token-guardian.ts +130 -9
- 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 +262 -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/router.ts +6 -0
- 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 +13 -1
- 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-GgF1IpQa.js +0 -67
|
@@ -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;
|
|
@@ -58,7 +58,7 @@ import { createAdmissionGate, ResourceAdmissionError, type AdmissionMetrics } fr
|
|
|
58
58
|
|
|
59
59
|
import { JAWCODE_CATALOG_AUGMENT_PROVIDERS, catalogModelSlug, shouldExposeRoutedModel } from "./parsing";
|
|
60
60
|
import type { CatalogModel } from "./parsing";
|
|
61
|
-
import { disabledNativeSlugs, hasComboTargets, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
61
|
+
import { disabledNativeSlugs, hasComboTargets, nativeInputModalities, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, nativeOpenAiSlugs, nativeParallelToolCalls, nativeReasoningEfforts } from "./metadata";
|
|
62
62
|
import { deriveComboCatalogModel, normalizedOpenAiApiSignature, openAiApiCollisionWarnings, replaceLastComboCatalogOmissions, warnUncataloguedComboOnce } from "./aggregation";
|
|
63
63
|
import type { ComboCatalogOmission } from "./aggregation";
|
|
64
64
|
|
|
@@ -274,10 +274,15 @@ export function warnDroppedConfiguredIdsOnce(name: string, droppedConfiguredIds:
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
export function isGlm52ModelId(id: string): boolean {
|
|
277
|
-
const normalized = id.toLowerCase();
|
|
277
|
+
const normalized = id.trim().toLowerCase();
|
|
278
278
|
return normalized === "glm-5.2" || normalized === "glm-5.2[1m]";
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
+
export function isGlm53ModelId(id: string): boolean {
|
|
282
|
+
const normalized = id.trim().toLowerCase();
|
|
283
|
+
return normalized === "glm-5.3" || normalized === "glm-5.3[1m]";
|
|
284
|
+
}
|
|
285
|
+
|
|
281
286
|
function plainRecord(value: unknown): Record<string, unknown> | undefined {
|
|
282
287
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
283
288
|
? value as Record<string, unknown>
|
|
@@ -358,6 +363,17 @@ function modelInputModalities(
|
|
|
358
363
|
value === "text" || value === "image" || value === "audio"
|
|
359
364
|
));
|
|
360
365
|
if (explicit && explicit.length > 0) return explicit;
|
|
366
|
+
const architecture = plainRecord(item.architecture);
|
|
367
|
+
const architectureModality = typeof architecture?.modality === "string"
|
|
368
|
+
? normalizedMetadataString(architecture.modality, 64)
|
|
369
|
+
: undefined;
|
|
370
|
+
if (architectureModality?.includes("->")) {
|
|
371
|
+
const [rawInput = ""] = architectureModality.split("->");
|
|
372
|
+
const inferred = rawInput
|
|
373
|
+
.split("+")
|
|
374
|
+
.filter(value => value === "text" || value === "image" || value === "audio");
|
|
375
|
+
if (inferred.length > 0) return [...new Set(inferred)];
|
|
376
|
+
}
|
|
361
377
|
if (capabilityRecord?.vision === false) return ["text"];
|
|
362
378
|
if (capabilityRecord?.vision === true || capabilities?.some(value => value === "vision" || value === "image-input")) {
|
|
363
379
|
return ["text", "image"];
|
|
@@ -385,9 +401,11 @@ export function catalogHintsFromModelsApiItem(providerName: string, item: Provid
|
|
|
385
401
|
? sanitizeCodexReasoningEfforts(listedReasoningEfforts)
|
|
386
402
|
: typeof rawReasoningEfforts === "boolean"
|
|
387
403
|
? (rawReasoningEfforts
|
|
388
|
-
? ((providerName === "neuralwatt" || providerName === "zai") &&
|
|
389
|
-
? ["low", "
|
|
390
|
-
:
|
|
404
|
+
? ((providerName === "neuralwatt" || providerName === "zai") && isGlm53ModelId(item.id)
|
|
405
|
+
? ["low", "high", "max"]
|
|
406
|
+
: (providerName === "neuralwatt" || providerName === "zai") && isGlm52ModelId(item.id)
|
|
407
|
+
? ["low", "medium", "high", "xhigh", "max"]
|
|
408
|
+
: ["low", "medium", "high", "xhigh"])
|
|
391
409
|
: [])
|
|
392
410
|
: undefined;
|
|
393
411
|
const capabilities = modelCapabilities(item);
|
|
@@ -756,7 +774,7 @@ async function gatherRoutedModelsUncached(
|
|
|
756
774
|
id: slug,
|
|
757
775
|
owned_by: "openai",
|
|
758
776
|
contextWindow,
|
|
759
|
-
maxInputTokens: contextWindow,
|
|
777
|
+
maxInputTokens: nativeOpenAiMaxInputTokens(slug) ?? contextWindow,
|
|
760
778
|
inputModalities: nativeInputModalities(slug),
|
|
761
779
|
reasoningEfforts: nativeReasoningEfforts(slug),
|
|
762
780
|
...(nativeParallelToolCalls(slug) ? { parallelToolCalls: true } : {}),
|
package/src/codex/catalog.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Public surface preserved exactly; importers keep using "src/codex/catalog".
|
|
3
3
|
export { isMediaGenerationModelId, shouldExposeRoutedModel, readCodexCatalogPath, readCatalog, normalizeRoutedCatalogEntry, catalogModelSlug, filterSupportedNativeSlugs, catalogModelSupportsReasoningSummaries } from "./catalog/parsing";
|
|
4
4
|
export type { CatalogModel, MultiAgentMode } from "./catalog/parsing";
|
|
5
|
-
export { NATIVE_OPENAI_MODELS, nativeOpenAiContextWindow, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort } from "./catalog/metadata";
|
|
5
|
+
export { NATIVE_DAYBREAK_BLUE_MODEL, NATIVE_GPT56_CONTEXT_WINDOW, NATIVE_GPT56_MAX_INPUT_TOKENS, NATIVE_OPENAI_MODELS, nativeOpenAiContextWindow, nativeOpenAiMaxInputTokens, disabledNativeSlugs, visibleNativeSlugs, desktopVisibleNativeSlugs, nativeModelRows, applyNativeVisibility, upstreamNativeEntry, nativeOpenAiSlugs, listCatalogNativeSlugs, nativeReasoningEfforts, nativeDefaultReasoningEffort } from "./catalog/metadata";
|
|
6
6
|
export { isSpawnableCodexCandidate, codexExecInvocation, loadBundledCodexCatalog, materializeBundledCodexCatalog, loadCatalogTemplate } from "./catalog/bundled";
|
|
7
7
|
export { nativeEffortClamp, shouldApplyNativeEffortClamp, catalogModelEfforts, codexSupportedReasoningEfforts, clampedDefaultEffort, clampEntryToCodexSupportedEfforts, clampCatalogModelsToCodexSupport } from "./catalog/effort";
|
|
8
8
|
export { applyProviderConfigHints, isDatedVariantId, filterCatalogVisibleModels, gatherRoutedModels, clearGatherRoutedModelsInflight, augmentRoutedModelsWithRegistryOpenAiApiRows, augmentRoutedModelsWithJawcodeMetadata } from "./catalog/provider-fetch";
|
|
@@ -19,6 +19,10 @@ const MAX_STICKY_LIMIT = 100;
|
|
|
19
19
|
const DEFAULT_STRATEGY: OcxAccountPoolRotationStrategy = "quota";
|
|
20
20
|
const VALID_STRATEGIES = new Set<OcxAccountPoolRotationStrategy>(["quota", "round-robin", "fill-first"]);
|
|
21
21
|
|
|
22
|
+
export const DEFAULT_ACCOUNT_PRIORITY = 0;
|
|
23
|
+
export const MIN_ACCOUNT_PRIORITY = -100;
|
|
24
|
+
export const MAX_ACCOUNT_PRIORITY = 100;
|
|
25
|
+
|
|
22
26
|
/** Strict parse for management APIs — returns null instead of defaulting. */
|
|
23
27
|
export function parseAccountPoolStrategy(raw: unknown): OcxAccountPoolRotationStrategy | null {
|
|
24
28
|
if (typeof raw === "string" && VALID_STRATEGIES.has(raw as OcxAccountPoolRotationStrategy)) {
|
|
@@ -43,6 +47,49 @@ export function normalizeAccountPoolStickyLimit(raw: unknown): number {
|
|
|
43
47
|
return parseAccountPoolStickyLimit(raw) ?? DEFAULT_STICKY_LIMIT;
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
export function parseAccountPriority(raw: unknown): number | null {
|
|
51
|
+
return typeof raw === "number"
|
|
52
|
+
&& Number.isInteger(raw)
|
|
53
|
+
&& raw >= MIN_ACCOUNT_PRIORITY
|
|
54
|
+
&& raw <= MAX_ACCOUNT_PRIORITY
|
|
55
|
+
? raw
|
|
56
|
+
: null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function normalizeAccountPriority(raw: unknown): number {
|
|
60
|
+
return parseAccountPriority(raw) ?? DEFAULT_ACCOUNT_PRIORITY;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Narrow eligible ids to the highest configured tier that still has quota headroom.
|
|
65
|
+
* Equal-priority pools and fully drained pools preserve the caller's exact list/order.
|
|
66
|
+
*/
|
|
67
|
+
export function selectPriorityTier(
|
|
68
|
+
ids: readonly string[],
|
|
69
|
+
priorityOf: (id: string) => number,
|
|
70
|
+
hasHeadroom: (id: string) => boolean,
|
|
71
|
+
pinnedId?: string,
|
|
72
|
+
): readonly string[] {
|
|
73
|
+
if (ids.length <= 1) return ids;
|
|
74
|
+
const priorities = ids.map(priorityOf);
|
|
75
|
+
const firstPriority = priorities[0]!;
|
|
76
|
+
if (priorities.every(priority => priority === firstPriority)) return ids;
|
|
77
|
+
|
|
78
|
+
let ceiling = Number.POSITIVE_INFINITY;
|
|
79
|
+
if (pinnedId !== undefined) {
|
|
80
|
+
const pinnedIndex = ids.indexOf(pinnedId);
|
|
81
|
+
if (pinnedIndex >= 0 && hasHeadroom(pinnedId)) ceiling = priorities[pinnedIndex]!;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const tiers = [...new Set(priorities)].sort((a, b) => b - a);
|
|
85
|
+
for (const tier of tiers) {
|
|
86
|
+
if (tier > ceiling) continue;
|
|
87
|
+
const members = ids.filter((_, index) => priorities[index] === tier);
|
|
88
|
+
if (members.some(hasHeadroom)) return members;
|
|
89
|
+
}
|
|
90
|
+
return ids;
|
|
91
|
+
}
|
|
92
|
+
|
|
46
93
|
function getOrCreateState(poolKey: string): SelectionState {
|
|
47
94
|
let state = selectionState.get(poolKey);
|
|
48
95
|
if (!state) {
|
|
@@ -60,7 +107,7 @@ function cloneSelectionState(state: SelectionState): SelectionState {
|
|
|
60
107
|
};
|
|
61
108
|
}
|
|
62
109
|
|
|
63
|
-
function smoothWeightedIndex(ids: string[], state: SelectionState): number {
|
|
110
|
+
function smoothWeightedIndex(ids: readonly string[], state: SelectionState): number {
|
|
64
111
|
let best = -1;
|
|
65
112
|
let bestScore = Number.NEGATIVE_INFINITY;
|
|
66
113
|
let total = 0;
|
|
@@ -87,7 +134,7 @@ function smoothWeightedIndex(ids: string[], state: SelectionState): number {
|
|
|
87
134
|
* either the live map entry or a scratch/clone for dry-run peek.
|
|
88
135
|
*/
|
|
89
136
|
function pickRoundRobinFromState(
|
|
90
|
-
eligibleIds: string[],
|
|
137
|
+
eligibleIds: readonly string[],
|
|
91
138
|
stickyLimit: number,
|
|
92
139
|
state: SelectionState,
|
|
93
140
|
commitSticky: boolean,
|
|
@@ -118,7 +165,7 @@ function pickRoundRobinFromState(
|
|
|
118
165
|
|
|
119
166
|
export function pickRoundRobinAccount(
|
|
120
167
|
poolKey: string,
|
|
121
|
-
eligibleIds: string[],
|
|
168
|
+
eligibleIds: readonly string[],
|
|
122
169
|
stickyLimit: number,
|
|
123
170
|
): string | null {
|
|
124
171
|
return pickRoundRobinFromState(eligibleIds, stickyLimit, getOrCreateState(poolKey), true);
|
|
@@ -130,7 +177,7 @@ export function pickRoundRobinAccount(
|
|
|
130
177
|
*/
|
|
131
178
|
export function peekRoundRobinAccount(
|
|
132
179
|
poolKey: string,
|
|
133
|
-
eligibleIds: string[],
|
|
180
|
+
eligibleIds: readonly string[],
|
|
134
181
|
stickyLimit: number,
|
|
135
182
|
): string | null {
|
|
136
183
|
const live = selectionState.get(poolKey);
|