@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/oauth/index.ts
CHANGED
|
@@ -249,12 +249,61 @@ export class UnsupportedOAuthProviderError extends Error {
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
export class OAuthLoginRequiredError extends Error {
|
|
252
|
+
readonly provider: string;
|
|
253
|
+
|
|
252
254
|
constructor(provider: string) {
|
|
253
255
|
super(`Not logged in to ${provider}. Run: ocx login ${provider}`);
|
|
254
256
|
this.name = "OAuthLoginRequiredError";
|
|
257
|
+
this.provider = provider;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export class OAuthProviderPublicationError extends Error {
|
|
262
|
+
constructor() {
|
|
263
|
+
super("OAuth credential was saved, but the provider entry was not written. Resolve the account namespace collision, then retry login.");
|
|
264
|
+
this.name = "OAuthProviderPublicationError";
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export class OAuthReauthIdentityMismatchError extends Error {
|
|
269
|
+
constructor() {
|
|
270
|
+
super("Signed-in account does not match the selected account. Sign in with the same account.");
|
|
271
|
+
this.name = "OAuthReauthIdentityMismatchError";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export class OAuthReauthIdentityUnverifiedError extends Error {
|
|
276
|
+
constructor() {
|
|
277
|
+
super("Could not verify signed-in account identity for reauth.");
|
|
278
|
+
this.name = "OAuthReauthIdentityUnverifiedError";
|
|
255
279
|
}
|
|
256
280
|
}
|
|
257
281
|
|
|
282
|
+
class OAuthLoginSupersededError extends Error {
|
|
283
|
+
constructor() {
|
|
284
|
+
super("OAuth login was superseded before credential persistence");
|
|
285
|
+
this.name = "OAuthLoginSupersededError";
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Project arbitrary OAuth failures onto the small, stable public error vocabulary. */
|
|
290
|
+
export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
|
|
291
|
+
if (error instanceof OAuthMutationBusyError) {
|
|
292
|
+
return error.message === "OAuth mutation queue wait timed out"
|
|
293
|
+
? "OAuth mutation queue wait timed out"
|
|
294
|
+
: "OAuth mutation queue is busy";
|
|
295
|
+
}
|
|
296
|
+
if (
|
|
297
|
+
(error instanceof OAuthLoginRequiredError && isOAuthProvider(error.provider))
|
|
298
|
+
|| error instanceof OAuthProviderPublicationError
|
|
299
|
+
|| error instanceof OAuthReauthIdentityMismatchError
|
|
300
|
+
|| error instanceof OAuthReauthIdentityUnverifiedError
|
|
301
|
+
|| error instanceof OAuthTokenRefreshBusyError
|
|
302
|
+
|| error instanceof OAuthTokenRefreshStaleError
|
|
303
|
+
) return error.message;
|
|
304
|
+
return "OAuth authentication failed. Check the OpenCodex account status and retry.";
|
|
305
|
+
}
|
|
306
|
+
|
|
258
307
|
function accessSnapshot(provider: string, accountId: string, cred: OAuthCredentials): OAuthAccessSnapshot {
|
|
259
308
|
const storedKiroRouting = {
|
|
260
309
|
...(cred.kiro?.profileArn ? { profileArn: cred.kiro.profileArn } : {}),
|
|
@@ -817,6 +866,7 @@ interface RunLoginDeps {
|
|
|
817
866
|
settleKiroLoginTransaction?: typeof settleKiroLoginTransaction;
|
|
818
867
|
removeAccount?: typeof removeAccount;
|
|
819
868
|
setActiveAccount?: typeof setActiveAccount;
|
|
869
|
+
assertCurrentOwner?: () => void;
|
|
820
870
|
}
|
|
821
871
|
|
|
822
872
|
/** Roll back only accounts created by this forced login, preserving concurrent refreshes of others. */
|
|
@@ -866,6 +916,7 @@ export async function runLogin(
|
|
|
866
916
|
const cred: OAuthCredentials = rawCred.source ? rawCred : { ...rawCred, source: "oauth" };
|
|
867
917
|
const settleKiroTransaction = deps.settleKiroLoginTransaction ?? settleKiroLoginTransaction;
|
|
868
918
|
try {
|
|
919
|
+
deps.assertCurrentOwner?.();
|
|
869
920
|
// Validate the provider row before credential persistence. A namespace claimed during the
|
|
870
921
|
// credential write is handled again below before the latest row is re-upserted.
|
|
871
922
|
if (provider !== "chatgpt") {
|
|
@@ -876,7 +927,7 @@ export async function runLogin(
|
|
|
876
927
|
const existing = getAccountCredential(provider, opts.reauthAccountId);
|
|
877
928
|
if (!existing) throw new Error(`Unknown account for reauth: ${opts.reauthAccountId}`);
|
|
878
929
|
if (!existing.accountId && !existing.email) {
|
|
879
|
-
throw new
|
|
930
|
+
throw new OAuthReauthIdentityUnverifiedError();
|
|
880
931
|
}
|
|
881
932
|
// Kiro social accounts share one device-scoped profile ARN, so reauth identity
|
|
882
933
|
// must compare the signed-in email first; otherwise reauthenticating a different
|
|
@@ -890,12 +941,15 @@ export async function runLogin(
|
|
|
890
941
|
? existing.email.toLowerCase() === cred.email.toLowerCase()
|
|
891
942
|
: false;
|
|
892
943
|
if (!identityMatches) {
|
|
893
|
-
throw new
|
|
944
|
+
throw new OAuthReauthIdentityMismatchError();
|
|
894
945
|
}
|
|
895
|
-
await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, cred
|
|
946
|
+
await (deps.saveAccountCredential ?? saveAccountCredential)(provider, opts.reauthAccountId, cred, {
|
|
947
|
+
assertBeforePersist: deps.assertCurrentOwner,
|
|
948
|
+
});
|
|
896
949
|
} else {
|
|
897
950
|
await (deps.saveCredential ?? saveCredential)(provider, cred, {
|
|
898
|
-
preserveIdentityless:
|
|
951
|
+
preserveIdentityless: opts?.forceLogin === true,
|
|
952
|
+
assertBeforePersist: deps.assertCurrentOwner,
|
|
899
953
|
});
|
|
900
954
|
}
|
|
901
955
|
if (provider !== "chatgpt") {
|
|
@@ -907,10 +961,7 @@ export async function runLogin(
|
|
|
907
961
|
provider,
|
|
908
962
|
);
|
|
909
963
|
if (lateCollision) {
|
|
910
|
-
throw new
|
|
911
|
-
`${lateCollision}. The credential for "${provider}" was saved, but the provider entry was not written. `
|
|
912
|
-
+ "Rename the account selector, then re-run the login.",
|
|
913
|
-
);
|
|
964
|
+
throw new OAuthProviderPublicationError();
|
|
914
965
|
}
|
|
915
966
|
upsertOAuthProvider(latestConfig, provider);
|
|
916
967
|
saveLatestConfig(latestConfig);
|
|
@@ -961,6 +1012,7 @@ export async function runLogin(
|
|
|
961
1012
|
*/
|
|
962
1013
|
const loginState = new Map<string, { error?: string; done: boolean }>();
|
|
963
1014
|
const loginAbort = new Map<string, AbortController>();
|
|
1015
|
+
const kiroLoginSettling = new Set<string>();
|
|
964
1016
|
|
|
965
1017
|
/** Pending paste for a login in progress: either a waiter or a stashed early submission. */
|
|
966
1018
|
interface ManualCodeSlot {
|
|
@@ -1117,13 +1169,14 @@ export async function startLoginFlow(
|
|
|
1117
1169
|
const def = OAUTH_PROVIDERS[provider];
|
|
1118
1170
|
if (!def) throw new UnsupportedOAuthProviderError(provider);
|
|
1119
1171
|
const existing = loginState.get(provider);
|
|
1120
|
-
if (existing && !existing.done) {
|
|
1172
|
+
if ((existing && !existing.done) || (provider === "kiro" && kiroLoginSettling.has(provider))) {
|
|
1121
1173
|
throw new Error(`A login for ${provider} is already in progress`);
|
|
1122
1174
|
}
|
|
1123
1175
|
clearManualCodeSlot(provider);
|
|
1124
1176
|
loginState.set(provider, { done: false });
|
|
1125
1177
|
const abort = new AbortController();
|
|
1126
1178
|
loginAbort.set(provider, abort);
|
|
1179
|
+
if (provider === "kiro") kiroLoginSettling.add(provider);
|
|
1127
1180
|
return new Promise((resolve, reject) => {
|
|
1128
1181
|
let urlResolved = false;
|
|
1129
1182
|
const ctrl: OAuthController = {
|
|
@@ -1136,7 +1189,14 @@ export async function startLoginFlow(
|
|
|
1136
1189
|
onManualCodeInput: (expectedState?: string) => waitForManualLoginCode(provider, abort.signal, expectedState),
|
|
1137
1190
|
signal: abort.signal,
|
|
1138
1191
|
};
|
|
1192
|
+
const abandonIfNotOwner = (error?: unknown): boolean => {
|
|
1193
|
+
if (loginAbort.get(provider) === abort) return false;
|
|
1194
|
+
if (!urlResolved) reject(error ?? new Error("OAuth login was superseded"));
|
|
1195
|
+
return true;
|
|
1196
|
+
};
|
|
1139
1197
|
const settle = async (error?: unknown): Promise<void> => {
|
|
1198
|
+
// A cancelled or superseded flow must not overwrite the newer owner's terminal state.
|
|
1199
|
+
if (abandonIfNotOwner(error)) return;
|
|
1140
1200
|
let finalError = error;
|
|
1141
1201
|
try {
|
|
1142
1202
|
await lifecycle?.onSettled?.();
|
|
@@ -1145,6 +1205,7 @@ export async function startLoginFlow(
|
|
|
1145
1205
|
// runtime config. For an already-failed login, keep the original recovery error.
|
|
1146
1206
|
if (finalError === undefined) finalError = settleError;
|
|
1147
1207
|
}
|
|
1208
|
+
if (abandonIfNotOwner(finalError)) return;
|
|
1148
1209
|
if (finalError === undefined) {
|
|
1149
1210
|
loginAbort.delete(provider);
|
|
1150
1211
|
clearManualCodeSlot(provider);
|
|
@@ -1158,22 +1219,28 @@ export async function startLoginFlow(
|
|
|
1158
1219
|
const e = finalError;
|
|
1159
1220
|
loginAbort.delete(provider);
|
|
1160
1221
|
clearManualCodeSlot(provider);
|
|
1161
|
-
const msg =
|
|
1222
|
+
const msg = publicOAuthAuthenticationErrorMessage(e);
|
|
1162
1223
|
loginState.set(provider, { done: true, error: msg });
|
|
1163
1224
|
if (!urlResolved) reject(e);
|
|
1164
1225
|
};
|
|
1165
1226
|
// Background: runLogin persists the credential + provider entry to disk. The lifecycle hook
|
|
1166
1227
|
// lets a long-lived server config adopt that settled state before clients observe done=true.
|
|
1167
|
-
|
|
1228
|
+
const assertCurrentOwner = (): void => {
|
|
1229
|
+
if (loginAbort.get(provider) !== abort) throw new OAuthLoginSupersededError();
|
|
1230
|
+
};
|
|
1231
|
+
void runLogin(provider, ctrl, opts, { assertCurrentOwner }).then(
|
|
1168
1232
|
() => settle(),
|
|
1169
1233
|
(e: unknown) => settle(e),
|
|
1170
1234
|
).catch((e: unknown) => {
|
|
1171
1235
|
// settle catches lifecycle failures, so this is only a defensive promise-boundary guard.
|
|
1236
|
+
if (abandonIfNotOwner(e)) return;
|
|
1172
1237
|
loginAbort.delete(provider);
|
|
1173
1238
|
clearManualCodeSlot(provider);
|
|
1174
|
-
const msg =
|
|
1239
|
+
const msg = publicOAuthAuthenticationErrorMessage(e);
|
|
1175
1240
|
loginState.set(provider, { done: true, error: msg });
|
|
1176
1241
|
if (!urlResolved) reject(e);
|
|
1242
|
+
}).finally(() => {
|
|
1243
|
+
if (provider === "kiro") kiroLoginSettling.delete(provider);
|
|
1177
1244
|
});
|
|
1178
1245
|
});
|
|
1179
1246
|
}
|
package/src/oauth/log.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/oauth/log.ts
|
|
2
2
|
import { maskAccountId } from "../lib/privacy";
|
|
3
|
+
import { redactSecretString } from "../lib/redact";
|
|
3
4
|
|
|
4
5
|
/** Normalize camelCase / snake_case / kebab-case field names before secret checks. */
|
|
5
6
|
function normalizeFieldKey(key: string): string {
|
|
@@ -20,6 +21,7 @@ const FORBIDDEN_NORMALIZED = new Set([
|
|
|
20
21
|
"id_token",
|
|
21
22
|
"client_secret",
|
|
22
23
|
"oauth_code",
|
|
24
|
+
"code_verifier",
|
|
23
25
|
"clientsecret",
|
|
24
26
|
]);
|
|
25
27
|
|
|
@@ -44,5 +46,5 @@ export function logOAuthEvent(
|
|
|
44
46
|
if (value === undefined) continue;
|
|
45
47
|
parts.push(`${key}=${String(value)}`);
|
|
46
48
|
}
|
|
47
|
-
console.info(parts.join(" "));
|
|
49
|
+
console.info(redactSecretString(parts.join(" ")));
|
|
48
50
|
}
|
package/src/oauth/store.ts
CHANGED
|
@@ -272,6 +272,17 @@ function newAccountId(cred: OAuthCredentials): string {
|
|
|
272
272
|
return createHash("sha256").update(identity).digest("hex").slice(0, 8);
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
/** Allocate a persisted slot id without reusing any existing account's ownership key. */
|
|
276
|
+
function distinctAccountId(cred: OAuthCredentials, accounts: readonly ProviderAccount[]): string {
|
|
277
|
+
const base = newAccountId(cred);
|
|
278
|
+
const occupied = new Set(accounts.map(account => account.id));
|
|
279
|
+
if (!occupied.has(base)) return base;
|
|
280
|
+
for (let suffix = 1; ; suffix += 1) {
|
|
281
|
+
const candidate = `${base}-${suffix}`;
|
|
282
|
+
if (!occupied.has(candidate)) return candidate;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
275
286
|
function normalizeAccount(value: unknown): ProviderAccount | null {
|
|
276
287
|
if (!value || typeof value !== "object") return null;
|
|
277
288
|
const candidate = value as Partial<ProviderAccount>;
|
|
@@ -425,10 +436,11 @@ function serializeMutation<T>(work: () => Promise<T>, retainedValues: readonly u
|
|
|
425
436
|
drainOAuthMutations();
|
|
426
437
|
return result;
|
|
427
438
|
}
|
|
428
|
-
export function mutateStore<T>(fn:(store:AuthStore)=>T|Promise<T>, retainedValues: readonly unknown[] = [], options?: { waitMs?: number }):Promise<T>{return serializeMutation(async()=>{const guard=await createOAuthFileLock({path:getAuthStoreLockPath(),staleAfterMs:30000}).acquire();try{
|
|
439
|
+
export function mutateStore<T>(fn:(store:AuthStore)=>T|Promise<T>, retainedValues: readonly unknown[] = [], options?: { waitMs?: number; assertBeforePersist?: () => void }):Promise<T>{return serializeMutation(async()=>{const guard=await createOAuthFileLock({path:getAuthStoreLockPath(),staleAfterMs:30000}).acquire();try{
|
|
429
440
|
const { store, hadLegacy } = loadAuthStoreInternal();
|
|
430
441
|
if (hadLegacy) backupLegacyOnce();
|
|
431
442
|
const result = await fn(store);
|
|
443
|
+
options?.assertBeforePersist?.();
|
|
432
444
|
persist(store);
|
|
433
445
|
return result;
|
|
434
446
|
}finally{guard.release();}}, retainedValues, options?.waitMs);
|
|
@@ -450,7 +462,7 @@ export function getCredential(provider: string): OAuthCredentials | null {
|
|
|
450
462
|
export async function saveCredential(
|
|
451
463
|
provider: string,
|
|
452
464
|
cred: OAuthCredentials,
|
|
453
|
-
opts: { preserveIdentityless?: boolean } = {},
|
|
465
|
+
opts: { preserveIdentityless?: boolean; assertBeforePersist?: () => void } = {},
|
|
454
466
|
): Promise<void> {
|
|
455
467
|
const safe = normalizeCredential(cred);
|
|
456
468
|
if (!safe) return;
|
|
@@ -503,22 +515,28 @@ export async function saveCredential(
|
|
|
503
515
|
delete active.needsReauth;
|
|
504
516
|
return;
|
|
505
517
|
}
|
|
506
|
-
const id =
|
|
518
|
+
const id = distinctAccountId(safe, set.accounts);
|
|
507
519
|
set.accounts.push({ id, credential: safe, addedAt: Date.now() });
|
|
508
520
|
set.activeAccountId = id;
|
|
509
521
|
return;
|
|
510
522
|
}
|
|
511
|
-
|
|
523
|
+
if (opts.preserveIdentityless) {
|
|
524
|
+
const id = distinctAccountId(safe, set.accounts);
|
|
525
|
+
set.accounts.push({ id, credential: safe, addedAt: Date.now() });
|
|
526
|
+
set.activeAccountId = id;
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
// No identity during a normal login: replace the active slot in place.
|
|
512
530
|
const active = set.accounts.find(a => a.id === set.activeAccountId);
|
|
513
531
|
if (active) {
|
|
514
532
|
active.credential = safe;
|
|
515
533
|
delete active.needsReauth;
|
|
516
534
|
} else {
|
|
517
|
-
const id =
|
|
535
|
+
const id = distinctAccountId(safe, set.accounts);
|
|
518
536
|
set.accounts.push({ id, credential: safe, addedAt: Date.now() });
|
|
519
537
|
set.activeAccountId = id;
|
|
520
538
|
}
|
|
521
|
-
}, [provider, safe]);
|
|
539
|
+
}, [provider, safe], { assertBeforePersist: opts.assertBeforePersist });
|
|
522
540
|
}
|
|
523
541
|
|
|
524
542
|
/** Remove the ACTIVE account; remaining accounts promote the first one. */
|
|
@@ -563,7 +581,12 @@ export function getAccountCredential(provider: string, accountId: string): OAuth
|
|
|
563
581
|
}
|
|
564
582
|
|
|
565
583
|
/** Persist a refreshed credential for a SPECIFIC account without touching activeAccountId. */
|
|
566
|
-
export async function saveAccountCredential(
|
|
584
|
+
export async function saveAccountCredential(
|
|
585
|
+
provider: string,
|
|
586
|
+
accountId: string,
|
|
587
|
+
cred: OAuthCredentials,
|
|
588
|
+
opts: { assertBeforePersist?: () => void } = {},
|
|
589
|
+
): Promise<void> {
|
|
567
590
|
const safe = normalizeCredential(cred);
|
|
568
591
|
if (!safe) return;
|
|
569
592
|
await mutateStore(store => {
|
|
@@ -571,7 +594,7 @@ export async function saveAccountCredential(provider: string, accountId: string,
|
|
|
571
594
|
if (!account) return;
|
|
572
595
|
account.credential = safe;
|
|
573
596
|
delete account.needsReauth;
|
|
574
|
-
}, [provider, accountId, safe]);
|
|
597
|
+
}, [provider, accountId, safe], { assertBeforePersist: opts.assertBeforePersist });
|
|
575
598
|
}
|
|
576
599
|
|
|
577
600
|
export async function setActiveAccount(provider: string, accountId: string): Promise<boolean> {
|
|
@@ -6,11 +6,22 @@
|
|
|
6
6
|
// receive the wire id (for example "Gemini 3.1 Pro (High)" => gemini-pro-agent), while the
|
|
7
7
|
// picker exposes collapsed base models with reasoning-effort routing.
|
|
8
8
|
|
|
9
|
+
const GEMINI_FLASH_CURRENT = "gemini-3.7-flash";
|
|
10
|
+
const RETIRED_FLASH_TIERS: Record<string, string> = {
|
|
11
|
+
"gemini-3.6-flash": "medium",
|
|
12
|
+
"gemini-3.6-flash-low": "low",
|
|
13
|
+
"gemini-3.6-flash-medium": "medium",
|
|
14
|
+
"gemini-3.6-flash-high": "high",
|
|
15
|
+
"gemini-3.5-flash-extra-low": "low",
|
|
16
|
+
"gemini-3.5-flash-low": "medium",
|
|
17
|
+
"gemini-3.5-flash-mid": "medium",
|
|
18
|
+
"gemini-3.5-flash-high": "high",
|
|
19
|
+
"gemini-3-flash-agent": "high",
|
|
20
|
+
};
|
|
21
|
+
|
|
9
22
|
// ── Wire IDs (what CCA :fetchAvailableModels returns) ──
|
|
10
23
|
const ANTIGRAVITY_WIRE_MODELS = [
|
|
11
|
-
|
|
12
|
-
"gemini-3.6-flash-medium",
|
|
13
|
-
"gemini-3.6-flash-high",
|
|
24
|
+
GEMINI_FLASH_CURRENT,
|
|
14
25
|
"gemini-3.1-pro-low",
|
|
15
26
|
"gemini-pro-agent",
|
|
16
27
|
"gemini-3.1-flash-image",
|
|
@@ -23,7 +34,7 @@ const ANTIGRAVITY_WIRE_MODELS = [
|
|
|
23
34
|
// Gemini models: effort → wire model suffix (official agy UI pattern).
|
|
24
35
|
// Claude Opus: effort → thinkingConfig.thinkingLevel (CLIProxyAPI proven pattern).
|
|
25
36
|
export const ANTIGRAVITY_MODEL_EFFORTS: Record<string, string[]> = {
|
|
26
|
-
|
|
37
|
+
[GEMINI_FLASH_CURRENT]: ["low", "medium", "high"],
|
|
27
38
|
"gemini-3.1-pro": ["low", "high"],
|
|
28
39
|
"claude-sonnet-4-6": ["low", "medium", "high", "max"],
|
|
29
40
|
"claude-opus-4-6-thinking": ["low", "medium", "high", "max"],
|
|
@@ -31,11 +42,6 @@ export const ANTIGRAVITY_MODEL_EFFORTS: Record<string, string[]> = {
|
|
|
31
42
|
|
|
32
43
|
// ── Effort → wire model map for Gemini base models ──
|
|
33
44
|
const ANTIGRAVITY_EFFORT_WIRE_MAP: Record<string, Record<string, string>> = {
|
|
34
|
-
"gemini-3.6-flash": {
|
|
35
|
-
low: "gemini-3.6-flash-low",
|
|
36
|
-
medium: "gemini-3.6-flash-medium",
|
|
37
|
-
high: "gemini-3.6-flash-high",
|
|
38
|
-
},
|
|
39
45
|
"gemini-3.1-pro": {
|
|
40
46
|
low: "gemini-3.1-pro-low",
|
|
41
47
|
high: "gemini-pro-agent",
|
|
@@ -44,11 +50,13 @@ const ANTIGRAVITY_EFFORT_WIRE_MAP: Record<string, Record<string, string>> = {
|
|
|
44
50
|
|
|
45
51
|
// ── Default effort per Gemini base model ──
|
|
46
52
|
const ANTIGRAVITY_DEFAULT_EFFORT: Record<string, string> = {
|
|
47
|
-
"gemini-3.6-flash": "medium",
|
|
48
53
|
"gemini-3.1-pro": "high",
|
|
49
54
|
};
|
|
50
55
|
|
|
51
|
-
const
|
|
56
|
+
const ANTIGRAVITY_THINKING_LEVEL_MODELS: Record<string, string> = {
|
|
57
|
+
[GEMINI_FLASH_CURRENT]: "medium",
|
|
58
|
+
};
|
|
59
|
+
const ANTIGRAVITY_THINKING_LEVELS = new Set(["low", "medium", "high"]);
|
|
52
60
|
|
|
53
61
|
function resolveAntigravityThinkingLevel(effort: string): string | undefined {
|
|
54
62
|
if (effort === "xhigh" || effort === "max" || effort === "ultra") return "high";
|
|
@@ -65,16 +73,9 @@ const ANTIGRAVITY_VISIBLE_MODEL_ALIASES: Record<string, string> = {
|
|
|
65
73
|
// Wire suffix IDs are identity aliases — they resolve to themselves so saved configs
|
|
66
74
|
// with explicit suffixes (e.g. gemini-3.6-flash-low) continue to work.
|
|
67
75
|
const ANTIGRAVITY_COMPATIBILITY_MODEL_ALIASES: Record<string, string> = {
|
|
68
|
-
"gemini-3.6-flash-low": "gemini-3.6-flash-low",
|
|
69
|
-
"gemini-3.6-flash-medium": "gemini-3.6-flash-medium",
|
|
70
|
-
"gemini-3.6-flash-high": "gemini-3.6-flash-high",
|
|
71
76
|
"gemini-3.1-pro-low": "gemini-3.1-pro-low",
|
|
72
77
|
"gemini-pro-agent": "gemini-pro-agent",
|
|
73
|
-
|
|
74
|
-
"gemini-3.5-flash-low": "gemini-3.6-flash-medium",
|
|
75
|
-
"gemini-3.5-flash-mid": "gemini-3.6-flash-medium",
|
|
76
|
-
"gemini-3.5-flash-high": "gemini-3.6-flash-high",
|
|
77
|
-
"gemini-3-flash-agent": "gemini-3.6-flash-high",
|
|
78
|
+
...Object.fromEntries(Object.keys(RETIRED_FLASH_TIERS).map(retired => [retired, GEMINI_FLASH_CURRENT])),
|
|
78
79
|
};
|
|
79
80
|
|
|
80
81
|
export const ANTIGRAVITY_MODEL_ALIASES: Record<string, string> = {
|
|
@@ -84,7 +85,7 @@ export const ANTIGRAVITY_MODEL_ALIASES: Record<string, string> = {
|
|
|
84
85
|
|
|
85
86
|
// Picker-visible: collapsed base models only.
|
|
86
87
|
export const ANTIGRAVITY_MODELS = [
|
|
87
|
-
|
|
88
|
+
GEMINI_FLASH_CURRENT,
|
|
88
89
|
"gemini-3.1-pro",
|
|
89
90
|
"gemini-3.1-flash-image",
|
|
90
91
|
"claude-sonnet-4-6",
|
|
@@ -94,9 +95,7 @@ export const ANTIGRAVITY_MODELS = [
|
|
|
94
95
|
|
|
95
96
|
// Context windows from the upstream `:fetchAvailableModels` maxTokens per model.
|
|
96
97
|
const ANTIGRAVITY_WIRE_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
97
|
-
|
|
98
|
-
"gemini-3.6-flash-medium": 1_048_576,
|
|
99
|
-
"gemini-3.6-flash-high": 1_048_576,
|
|
98
|
+
[GEMINI_FLASH_CURRENT]: 1_048_576,
|
|
100
99
|
"gemini-3.1-pro-low": 1_048_576,
|
|
101
100
|
"gemini-pro-agent": 1_048_576,
|
|
102
101
|
"gemini-3.1-flash-image": 1_048_576,
|
|
@@ -107,7 +106,7 @@ const ANTIGRAVITY_WIRE_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
|
107
106
|
|
|
108
107
|
export const ANTIGRAVITY_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
109
108
|
// Collapsed base IDs — explicit entries for the picker.
|
|
110
|
-
|
|
109
|
+
[GEMINI_FLASH_CURRENT]: 1_048_576,
|
|
111
110
|
"gemini-3.1-pro": 1_048_576,
|
|
112
111
|
// Wire IDs and aliases via derivation.
|
|
113
112
|
...ANTIGRAVITY_WIRE_MODEL_CONTEXT_WINDOWS,
|
|
@@ -119,6 +118,15 @@ export const ANTIGRAVITY_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
|
119
118
|
),
|
|
120
119
|
};
|
|
121
120
|
|
|
121
|
+
export const ANTIGRAVITY_MODEL_INPUT_MODALITIES: Record<string, string[]> = {
|
|
122
|
+
[GEMINI_FLASH_CURRENT]: ["text", "image"],
|
|
123
|
+
"gemini-3.1-pro": ["text", "image"],
|
|
124
|
+
"gemini-3.1-flash-image": ["text", "image"],
|
|
125
|
+
"claude-sonnet-4-6": ["text", "image"],
|
|
126
|
+
"claude-opus-4-6-thinking": ["text", "image"],
|
|
127
|
+
"gpt-oss-120b-medium": ["text"],
|
|
128
|
+
};
|
|
129
|
+
|
|
122
130
|
export function resolveAntigravityWireModelId(modelId: string): string {
|
|
123
131
|
return ANTIGRAVITY_MODEL_ALIASES[modelId] ?? modelId;
|
|
124
132
|
}
|
|
@@ -132,6 +140,10 @@ export function isAntigravitySuffixModelId(modelId: string): boolean {
|
|
|
132
140
|
return !(ANTIGRAVITY_MODELS as string[]).includes(modelId);
|
|
133
141
|
}
|
|
134
142
|
|
|
143
|
+
export function retiredAntigravityFlashTier(modelId: string): string | undefined {
|
|
144
|
+
return RETIRED_FLASH_TIERS[modelId];
|
|
145
|
+
}
|
|
146
|
+
|
|
135
147
|
/**
|
|
136
148
|
* Resolve a picker-visible base model + optional reasoning effort to the CCA wire model ID.
|
|
137
149
|
*
|
|
@@ -146,11 +158,27 @@ export function resolveAntigravityEffortWireModel(
|
|
|
146
158
|
modelId: string,
|
|
147
159
|
effort?: string,
|
|
148
160
|
): { wireModelId: string; thinkingLevel?: string } {
|
|
161
|
+
const retiredTier = RETIRED_FLASH_TIERS[modelId];
|
|
162
|
+
if (retiredTier) {
|
|
163
|
+
return {
|
|
164
|
+
wireModelId: GEMINI_FLASH_CURRENT,
|
|
165
|
+
thinkingLevel: effort ? resolveAntigravityThinkingLevel(effort) ?? retiredTier : retiredTier,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
149
169
|
// Rule 1: suffix/compat alias — suffix IS the effort.
|
|
150
170
|
if (isAntigravitySuffixModelId(modelId)) {
|
|
151
171
|
return { wireModelId: resolveAntigravityWireModelId(modelId) };
|
|
152
172
|
}
|
|
153
173
|
|
|
174
|
+
const defaultLevel = ANTIGRAVITY_THINKING_LEVEL_MODELS[modelId];
|
|
175
|
+
if (defaultLevel) {
|
|
176
|
+
return {
|
|
177
|
+
wireModelId: modelId,
|
|
178
|
+
thinkingLevel: effort ? resolveAntigravityThinkingLevel(effort) ?? defaultLevel : defaultLevel,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
154
182
|
// Rule 2/3: mapped Gemini base model.
|
|
155
183
|
const effortMap = ANTIGRAVITY_EFFORT_WIRE_MAP[modelId];
|
|
156
184
|
if (effortMap) {
|
|
@@ -189,6 +217,7 @@ const ANTIGRAVITY_USAGE_BASE_BY_ID: Record<string, string> = (() => {
|
|
|
189
217
|
// If alias is itself a base/wire already mapped, keep that mapping.
|
|
190
218
|
else if (rev[wire]) rev[alias] = rev[wire]!;
|
|
191
219
|
}
|
|
220
|
+
for (const retired of Object.keys(RETIRED_FLASH_TIERS)) rev[retired] = retired;
|
|
192
221
|
// Visible aliases that only appear in ANTIGRAVITY_VISIBLE_MODEL_ALIASES are already
|
|
193
222
|
// included via ANTIGRAVITY_MODEL_ALIASES. Identity bases without effort maps remain.
|
|
194
223
|
return rev;
|