@bitkyc08/opencodex 2.25.0 → 2.26.0
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/bin/ocx.mjs +59 -7
- package/gui/dist/assets/index-RL6b1bTV.js +102 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/base.ts +18 -0
- package/src/adapters/client-fingerprint.ts +0 -2
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/live-models.ts +117 -30
- package/src/adapters/cursor/live-transport.ts +137 -56
- package/src/adapters/cursor/native-exec-fs.ts +1 -1
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/protobuf-request.ts +89 -21
- package/src/adapters/cursor/tool-definitions.ts +18 -7
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +7 -0
- package/src/adapters/cursor.ts +2 -0
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google.ts +23 -3
- package/src/adapters/openai-chat.ts +38 -13
- package/src/adapters/openai-responses.ts +129 -9
- package/src/adapters/registry.ts +30 -1
- package/src/bridge.ts +50 -12
- package/src/chat/inbound.ts +1 -0
- package/src/claude/agents-inject.ts +3 -2
- package/src/cli/dispatch.ts +19 -6
- package/src/cli/help.ts +2 -1
- package/src/cli/integrations.ts +35 -0
- package/src/cli/minimax.ts +8 -2
- package/src/cli/registry.ts +13 -2
- package/src/clients/config-export.ts +120 -1
- package/src/codex/account-store.ts +17 -1
- package/src/codex/auth-api.ts +51 -17
- package/src/codex/catalog/effort.ts +8 -5
- package/src/codex/catalog/provider-fetch.ts +39 -27
- package/src/codex/catalog/sync.ts +30 -8
- package/src/codex/inject.ts +39 -13
- package/src/codex/main-account.ts +29 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +25 -0
- package/src/codex/prompt-journal.ts +6 -2
- package/src/codex/quota-rejection.ts +21 -7
- package/src/codex/refresh.ts +4 -2
- package/src/codex/sync.ts +106 -3
- package/src/codex/warmup.ts +187 -81
- package/src/config.ts +85 -46
- package/src/generated/compatibility-version.json +127 -83
- package/src/grok/inject.ts +1 -1
- package/src/images/loop.ts +1 -0
- package/src/integrations/registry.ts +7 -0
- package/src/lab/automation/config-persistence.ts +2 -2
- package/src/lab/automation/persistence.ts +2 -2
- package/src/lab/ledger/purge.ts +2 -2
- package/src/lab/subject/behavior-fingerprint.ts +2 -2
- package/src/lib/config-ownership.ts +5 -2
- package/src/lib/destination-policy.ts +18 -1
- package/src/lib/provider-outbound.ts +7 -0
- package/src/lib/redact.ts +11 -0
- package/src/lib/tool-argument-integers.ts +50 -6
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-retry.ts +2 -1
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/oauth/google-antigravity.ts +2 -2
- package/src/oauth/index.ts +60 -8
- package/src/providers/antigravity-models.ts +58 -4
- package/src/providers/command-code-efforts.ts +36 -9
- package/src/providers/context-cap.ts +9 -0
- package/src/providers/derive.ts +4 -0
- package/src/providers/fastwire.ts +453 -0
- package/src/providers/registry.ts +21 -1
- package/src/providers/service-tier.ts +173 -89
- package/src/responses/parser.ts +25 -16
- package/src/responses/reasoning-replay-cache.ts +30 -0
- package/src/responses/thought-signature-replay.ts +74 -4
- package/src/router.ts +6 -0
- package/src/routing/compatibility/behavior.ts +27 -16
- package/src/server/index.ts +10 -1
- package/src/server/management/oauth-account-routes.ts +10 -2
- package/src/server/management/shared.ts +1 -1
- package/src/server/management/system-routes.ts +15 -0
- package/src/server/request-log.ts +57 -3
- package/src/server/responses/agent-task-recovery.ts +6 -1
- package/src/server/responses/core.ts +209 -40
- package/src/server/responses/fetch-helpers.ts +15 -36
- package/src/server/responses/responses-field-backfill.ts +173 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +171 -0
- package/src/service.ts +38 -36
- package/src/storage/cleanup.ts +2 -2
- package/src/tray/windows.ts +3 -2
- package/src/types.ts +101 -14
- package/src/update/job.ts +9 -18
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +34 -5
- package/src/usage/log.ts +74 -4
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/web-search/anthropic-executor.ts +8 -6
- package/gui/dist/assets/index-DxJ7kXj9.js +0 -102
|
@@ -416,6 +416,22 @@ export function mcodeConfigPath(env: OpencodeLaunchEnv = process.env, home: stri
|
|
|
416
416
|
return join(mcodeHomeDir(env, home), "config.yaml");
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
+
/**
|
|
420
|
+
* ZCode (Z.ai's desktop client) keeps everything under `~/.zcode`; custom
|
|
421
|
+
* providers live in `v2/config.json`. `ZCODE_DATA_DIR` mirrors the other
|
|
422
|
+
* clients' override convention; relative overrides are refused for the same
|
|
423
|
+
* reason as MCode's.
|
|
424
|
+
*/
|
|
425
|
+
export function zcodeHomeDir(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
|
|
426
|
+
const override = env.ZCODE_DATA_DIR?.trim();
|
|
427
|
+
if (override) return absoluteClientPath(override, home, "ZCODE_DATA_DIR");
|
|
428
|
+
return join(home, ".zcode");
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function zcodeConfigPath(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
|
|
432
|
+
return join(zcodeHomeDir(env, home), "v2", "config.json");
|
|
433
|
+
}
|
|
434
|
+
|
|
419
435
|
/**
|
|
420
436
|
* One proxy-routed model destined for a client config. Deliberately narrower than
|
|
421
437
|
* `CatalogModel` so a serializer cannot reach for a field that does not survive the
|
|
@@ -456,7 +472,8 @@ export type ExportClientId =
|
|
|
456
472
|
| "kimi"
|
|
457
473
|
| "gajae"
|
|
458
474
|
| "dsh"
|
|
459
|
-
| "mcode"
|
|
475
|
+
| "mcode"
|
|
476
|
+
| "zcode";
|
|
460
477
|
|
|
461
478
|
export interface ExportClientSpec {
|
|
462
479
|
id: ExportClientId;
|
|
@@ -876,6 +893,36 @@ export interface McodeGeneratedConfig {
|
|
|
876
893
|
custom_provider: Record<string, McodeProviderBlock>;
|
|
877
894
|
}
|
|
878
895
|
|
|
896
|
+
/**
|
|
897
|
+
* ZCode's `~/.zcode/v2/config.json` provider entry (observed schema, validated
|
|
898
|
+
* live against ZCode 3.7.7). `kind: "anthropic"` selects the Anthropic
|
|
899
|
+
* Messages protocol, which the proxy serves at `/v1/messages`. `apiKeyRequired`
|
|
900
|
+
* keeps ZCode's UI from prompting for a key it does not need on loopback; the
|
|
901
|
+
* serialized key is always the non-secret loopback placeholder.
|
|
902
|
+
*/
|
|
903
|
+
export interface ZcodeModelEntry {
|
|
904
|
+
name?: string;
|
|
905
|
+
limit?: { context: number; output?: number };
|
|
906
|
+
modalities: { input: string[]; output: string[] };
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
export interface ZcodeProviderBlock {
|
|
910
|
+
name: "OpenCodex";
|
|
911
|
+
kind: "anthropic";
|
|
912
|
+
enabled: true;
|
|
913
|
+
source: "custom";
|
|
914
|
+
options: {
|
|
915
|
+
apiKey: string;
|
|
916
|
+
baseURL: string;
|
|
917
|
+
apiKeyRequired: true;
|
|
918
|
+
};
|
|
919
|
+
models: Record<string, ZcodeModelEntry>;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
export interface ZcodeGeneratedConfig {
|
|
923
|
+
provider: Record<string, ZcodeProviderBlock>;
|
|
924
|
+
}
|
|
925
|
+
|
|
879
926
|
/**
|
|
880
927
|
* Pi's `~/.pi/agent/models.json` shape. `models` is an ARRAY (identity lives in `id`),
|
|
881
928
|
* unlike OpenCode's keyed object.
|
|
@@ -1208,6 +1255,53 @@ function buildMcodeClientConfig(ctx: ExportContext): McodeGeneratedConfig {
|
|
|
1208
1255
|
};
|
|
1209
1256
|
}
|
|
1210
1257
|
|
|
1258
|
+
/**
|
|
1259
|
+
* ZCode dials the Anthropic Messages surface, so `baseURL` is the proxy origin
|
|
1260
|
+
* without the `/v1` suffix (ZCode appends `/v1/messages` itself — the same
|
|
1261
|
+
* shape its builtin Z.ai providers use). Model ids are the proxy's canonical
|
|
1262
|
+
* `provider/id` selectors, which `/v1/messages` resolves directly. Context
|
|
1263
|
+
* limits follow the authoritative-window rule: a model without one ships
|
|
1264
|
+
* without `limit` rather than guessing. Modalities are ZCode's observed
|
|
1265
|
+
* `text`-floor vocabulary; image-capable rows advertise image input.
|
|
1266
|
+
*/
|
|
1267
|
+
function buildZcodeClientConfig(ctx: ExportContext): ZcodeGeneratedConfig {
|
|
1268
|
+
const models: Record<string, ZcodeModelEntry> = {};
|
|
1269
|
+
for (const model of normalizeExportModels(ctx.models)) {
|
|
1270
|
+
const input = inputModalitiesForClient("pi", model.inputModalities);
|
|
1271
|
+
if (input === null) continue;
|
|
1272
|
+
const entry: ZcodeModelEntry = {
|
|
1273
|
+
name: exportModelLabel(model),
|
|
1274
|
+
modalities: { input, output: ["text"] },
|
|
1275
|
+
};
|
|
1276
|
+
// `limit.context` follows the authoritative-window rule. `output` is
|
|
1277
|
+
// deliberately absent: ZCode's schema makes it optional and we have no
|
|
1278
|
+
// authoritative output budget to assert (reviewer finding: an emitted
|
|
1279
|
+
// stand-in would be a guessed capability, exactly what "no metadata is
|
|
1280
|
+
// guessed" forbids).
|
|
1281
|
+
const context = authoritativeContextWindow(model.contextWindow);
|
|
1282
|
+
if (context !== undefined) {
|
|
1283
|
+
entry.limit = { context };
|
|
1284
|
+
}
|
|
1285
|
+
models[model.namespaced] = entry;
|
|
1286
|
+
}
|
|
1287
|
+
return {
|
|
1288
|
+
provider: {
|
|
1289
|
+
[OPENCODE_PROVIDER_ID]: {
|
|
1290
|
+
name: "OpenCodex",
|
|
1291
|
+
kind: "anthropic",
|
|
1292
|
+
enabled: true,
|
|
1293
|
+
source: "custom",
|
|
1294
|
+
options: {
|
|
1295
|
+
apiKey: LOOPBACK_API_KEY_PLACEHOLDER,
|
|
1296
|
+
baseURL: ctx.baseUrl.replace(/\/v1\/?$/, ""),
|
|
1297
|
+
apiKeyRequired: true,
|
|
1298
|
+
},
|
|
1299
|
+
models,
|
|
1300
|
+
},
|
|
1301
|
+
},
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1211
1305
|
/**
|
|
1212
1306
|
* Per-client model counts, read back off the SERIALIZED document rather than
|
|
1213
1307
|
* recomputed from the input rows: `modelsWithoutLimits` drives a GUI line about
|
|
@@ -1263,6 +1357,11 @@ function summarizeMcode(document: unknown): { modelCount: number; modelsWithoutL
|
|
|
1263
1357
|
return { modelCount: models.length, modelsWithoutLimits: 0 };
|
|
1264
1358
|
}
|
|
1265
1359
|
|
|
1360
|
+
function summarizeZcode(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
|
|
1361
|
+
const models = Object.values((document as ZcodeGeneratedConfig | undefined)?.provider?.[OPENCODE_PROVIDER_ID]?.models ?? {});
|
|
1362
|
+
return { modelCount: models.length, modelsWithoutLimits: models.filter(model => !model.limit).length };
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1266
1365
|
/** One fragment at `path`, built from this client's own document. */
|
|
1267
1366
|
function singleFragment(clientId: ExportClientId, path: readonly string[], value: unknown): ManagedContribution {
|
|
1268
1367
|
return { clientId, fragments: [{ path, value }] };
|
|
@@ -1324,6 +1423,11 @@ function buildMcodeContribution(ctx: ExportContext): ManagedContribution {
|
|
|
1324
1423
|
return singleFragment("mcode", ["custom_provider", OPENCODE_PROVIDER_ID], doc.custom_provider[OPENCODE_PROVIDER_ID]);
|
|
1325
1424
|
}
|
|
1326
1425
|
|
|
1426
|
+
function buildZcodeContribution(ctx: ExportContext): ManagedContribution {
|
|
1427
|
+
const doc = buildZcodeClientConfig(ctx);
|
|
1428
|
+
return singleFragment("zcode", ["provider", OPENCODE_PROVIDER_ID], doc.provider[OPENCODE_PROVIDER_ID]);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1327
1431
|
export const EXPORT_CLIENTS: Record<ExportClientId, ExportClientSpec> = {
|
|
1328
1432
|
opencode: {
|
|
1329
1433
|
id: "opencode",
|
|
@@ -1447,6 +1551,21 @@ export const EXPORT_CLIENTS: Record<ExportClientId, ExportClientSpec> = {
|
|
|
1447
1551
|
// header field, so real keys are never serialized and remote binds refuse.
|
|
1448
1552
|
loopbackOnly: true,
|
|
1449
1553
|
},
|
|
1554
|
+
zcode: {
|
|
1555
|
+
id: "zcode",
|
|
1556
|
+
filename: "config.json",
|
|
1557
|
+
destination: env => zcodeConfigPath(env),
|
|
1558
|
+
apiKeyEnv: "",
|
|
1559
|
+
exportHint: "ZCode reads a non-secret placeholder from v2/config.json; loopback needs no key.",
|
|
1560
|
+
build: buildZcodeClientConfig,
|
|
1561
|
+
format: "json",
|
|
1562
|
+
summarize: summarizeZcode,
|
|
1563
|
+
buildContribution: buildZcodeContribution,
|
|
1564
|
+
// ZCode persists the credential in its own file and has no dedicated
|
|
1565
|
+
// proxy-admission header field, so real keys are never serialized and
|
|
1566
|
+
// remote binds refuse — same reasoning as MCode.
|
|
1567
|
+
loopbackOnly: true,
|
|
1568
|
+
},
|
|
1450
1569
|
};
|
|
1451
1570
|
|
|
1452
1571
|
export const EXPORT_CLIENT_IDS: readonly ExportClientId[] = Object.keys(EXPORT_CLIENTS) as ExportClientId[];
|
|
@@ -386,6 +386,19 @@ function findFreshCredentialForGrant(
|
|
|
386
386
|
return null;
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
async function notePlanFromRefreshedAccessToken(
|
|
390
|
+
id: string,
|
|
391
|
+
accessToken: string,
|
|
392
|
+
generation: number,
|
|
393
|
+
): Promise<void> {
|
|
394
|
+
try {
|
|
395
|
+
const { noteCodexAccountAccessToken } = await import("./plan-from-token");
|
|
396
|
+
noteCodexAccountAccessToken(id, accessToken, generation);
|
|
397
|
+
} catch {
|
|
398
|
+
// Derived plan metadata must not fail credential refresh.
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
389
402
|
export async function getValidCodexToken(id: string): Promise<CodexTokenResult> {
|
|
390
403
|
const record = readCodexAccountRecord(id);
|
|
391
404
|
const cred = record?.deletedAt == null ? record?.credential : undefined;
|
|
@@ -415,10 +428,12 @@ export async function getValidCodexToken(id: string): Promise<CodexTokenResult>
|
|
|
415
428
|
if (!saveCodexAccountCredentialIfGeneration(id, current.generation, refreshed.credential)) {
|
|
416
429
|
throw new CodexCredentialGenerationConflictError();
|
|
417
430
|
}
|
|
431
|
+
const generation = current.generation + 1;
|
|
432
|
+
await notePlanFromRefreshedAccessToken(id, refreshed.credential.accessToken, generation);
|
|
418
433
|
return {
|
|
419
434
|
accessToken: refreshed.credential.accessToken,
|
|
420
435
|
chatgptAccountId: refreshed.credential.chatgptAccountId,
|
|
421
|
-
generation
|
|
436
|
+
generation,
|
|
422
437
|
};
|
|
423
438
|
}
|
|
424
439
|
return getValidCodexToken(id);
|
|
@@ -520,6 +535,7 @@ export async function getValidCodexToken(id: string): Promise<CodexTokenResult>
|
|
|
520
535
|
flight = { promise: refreshPromise, startedAt: Date.now(), abort };
|
|
521
536
|
refreshLocks.set(refreshGrantFingerprint, flight);
|
|
522
537
|
const result = await refreshPromise;
|
|
538
|
+
await notePlanFromRefreshedAccessToken(id, result.accessToken, result.generation);
|
|
523
539
|
return {
|
|
524
540
|
accessToken: result.accessToken,
|
|
525
541
|
chatgptAccountId: result.chatgptAccountId,
|
package/src/codex/auth-api.ts
CHANGED
|
@@ -83,7 +83,7 @@ export {
|
|
|
83
83
|
updateAccountQuota,
|
|
84
84
|
} from "./quota";
|
|
85
85
|
import { extractAccountId } from "../oauth/chatgpt";
|
|
86
|
-
import { getMainAccountPlan, MAIN_CODEX_ACCOUNT_ID, setMainAccountPlan } from "./main-account";
|
|
86
|
+
import { getMainAccountPlan, isMainAccountTokenVerifiablyLive, MAIN_CODEX_ACCOUNT_ID, setMainAccountPlan } from "./main-account";
|
|
87
87
|
import { captureConfigGeneration, registerStateSweepAfterTick } from "../lib/state-store-sweeper";
|
|
88
88
|
import { reconcileLiveStateStores } from "../lib/state-store-registrations";
|
|
89
89
|
import {
|
|
@@ -98,7 +98,7 @@ import {
|
|
|
98
98
|
} from "./main-account-cache";
|
|
99
99
|
export { clearMainAccountInfoCache } from "./main-account-cache";
|
|
100
100
|
import { maskEmail } from "../lib/privacy";
|
|
101
|
-
import {
|
|
101
|
+
import { codexWarmupFailureReason, warmCodexAccount } from "./warmup";
|
|
102
102
|
export { maskEmail } from "../lib/privacy";
|
|
103
103
|
import type { CodexAccount, CodexAccountCredentials, OcxConfig } from "../types";
|
|
104
104
|
import type { CatalogDisposition } from "./convergence-types";
|
|
@@ -396,13 +396,10 @@ async function verifyCodexAccountWarmup(
|
|
|
396
396
|
return { ok: true, validatedAt: Date.now() };
|
|
397
397
|
} catch (err) {
|
|
398
398
|
const reason = codexWarmupFailureReason(err);
|
|
399
|
-
const upstream = err instanceof CodexWarmupError ? err.upstreamDetail : undefined;
|
|
400
399
|
return {
|
|
401
400
|
ok: false,
|
|
402
401
|
response: jsonResponse({
|
|
403
|
-
error:
|
|
404
|
-
? `Codex account warmup failed: ${upstream}`
|
|
405
|
-
: "Codex account warmup failed. Reauthenticate the account and try again.",
|
|
402
|
+
error: "Codex account warmup failed. Reauthenticate the account and try again.",
|
|
406
403
|
code: "codex_warmup_failed",
|
|
407
404
|
reason,
|
|
408
405
|
accountId,
|
|
@@ -569,12 +566,31 @@ const MAIN_TERMINAL_AUTH_CODES = new Set([
|
|
|
569
566
|
"invalid_refresh_token",
|
|
570
567
|
]);
|
|
571
568
|
|
|
572
|
-
|
|
573
|
-
|
|
569
|
+
/**
|
|
570
|
+
* A WHAM 401 is not itself proof the local credential died. Upstream edges can
|
|
571
|
+
* transiently reject a still-valid access token (region/anti-abuse/rotation
|
|
572
|
+
* races), and fail-closing on every bare 401 makes a healthy main account flip
|
|
573
|
+
* needs-reauth on the next GUI quota poll. Only treat the response as terminal
|
|
574
|
+
* when the body carries a known terminal code or the local access token is not
|
|
575
|
+
* verifiably live (`accessTokenLive`). Liveness must be strict: a JWT whose
|
|
576
|
+
* `exp` cannot be decoded is NOT live — an undecodable token that vouched for
|
|
577
|
+
* itself would make a real 401 permanently transient.
|
|
578
|
+
*/
|
|
579
|
+
async function isTerminalMainAuthResponse(resp: Response, accessTokenLive: boolean): Promise<boolean> {
|
|
580
|
+
if (resp.status === 401) {
|
|
581
|
+
if (!accessTokenLive) return true;
|
|
582
|
+
const code = await readMainAuthErrorCode(resp);
|
|
583
|
+
return typeof code === "string" && MAIN_TERMINAL_AUTH_CODES.has(code);
|
|
584
|
+
}
|
|
574
585
|
if (resp.status !== 403) return false;
|
|
586
|
+
const code = await readMainAuthErrorCode(resp);
|
|
587
|
+
return typeof code === "string" && MAIN_TERMINAL_AUTH_CODES.has(code);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async function readMainAuthErrorCode(resp: Response): Promise<unknown> {
|
|
575
591
|
try {
|
|
576
592
|
const body = await readBoundedResponseBody(resp, { totalTimeoutMs: 1_000, inactivityTimeoutMs: 1_000 });
|
|
577
|
-
if (!body.displaySafe) return
|
|
593
|
+
if (!body.displaySafe) return undefined;
|
|
578
594
|
const parsed = JSON.parse(body.text) as {
|
|
579
595
|
detail?: { code?: unknown } | string;
|
|
580
596
|
error?: { code?: unknown } | string;
|
|
@@ -585,9 +601,9 @@ async function isTerminalMainAuthResponse(resp: Response): Promise<boolean> {
|
|
|
585
601
|
: typeof parsed.error === "object" && parsed.error !== null
|
|
586
602
|
? parsed.error.code
|
|
587
603
|
: parsed.code;
|
|
588
|
-
return
|
|
604
|
+
return code;
|
|
589
605
|
} catch {
|
|
590
|
-
return
|
|
606
|
+
return undefined;
|
|
591
607
|
}
|
|
592
608
|
}
|
|
593
609
|
|
|
@@ -707,7 +723,7 @@ async function fetchMainAccountInfoWhileOwned(
|
|
|
707
723
|
signal: AbortSignal.timeout(8000),
|
|
708
724
|
});
|
|
709
725
|
if (!resp.ok) {
|
|
710
|
-
const terminalAuthFailure = await isTerminalMainAuthResponse(resp);
|
|
726
|
+
const terminalAuthFailure = await isTerminalMainAuthResponse(resp, isMainAccountTokenVerifiablyLive());
|
|
711
727
|
const retried = await retryMainAccountInfoIfIdentityChanged(requestAccountId, retriesRemaining, nativeMainLease);
|
|
712
728
|
if (retried) return retried;
|
|
713
729
|
if (terminalAuthFailure) {
|
|
@@ -854,6 +870,15 @@ function reconcileFreshPoolAccountPlans(runtimeConfig: OcxConfig, updates: Fresh
|
|
|
854
870
|
accepted.push(update);
|
|
855
871
|
if (persistedAccount.plan !== update.plan) {
|
|
856
872
|
persistedAccount.plan = update.plan;
|
|
873
|
+
// WHAM is the authoritative plan source: stamp provenance so a later JWT
|
|
874
|
+
// reconcile cannot overwrite this observation within the same credential
|
|
875
|
+
// generation (src/codex/plan-from-token.ts jwtMayWritePlan). Stamped only
|
|
876
|
+
// alongside a real plan change: a steady-state refresh whose plan is
|
|
877
|
+
// unchanged must stay write-free (no-config-write contract), and an
|
|
878
|
+
// unchanged value needs no fence — a JWT rewrite to the same text is a
|
|
879
|
+
// no-op under the caller's own equality check.
|
|
880
|
+
persistedAccount.planSource = "wham";
|
|
881
|
+
persistedAccount.planCredentialGeneration = update.credentialGeneration;
|
|
857
882
|
changed = true;
|
|
858
883
|
}
|
|
859
884
|
}
|
|
@@ -873,6 +898,8 @@ function reconcileFreshPoolAccountPlans(runtimeConfig: OcxConfig, updates: Fresh
|
|
|
873
898
|
const liveAccount = configuredPoolAccount(runtimeConfig, update.accountId);
|
|
874
899
|
if (liveAccount) {
|
|
875
900
|
liveAccount.plan = update.plan;
|
|
901
|
+
liveAccount.planSource = "wham";
|
|
902
|
+
liveAccount.planCredentialGeneration = update.credentialGeneration;
|
|
876
903
|
}
|
|
877
904
|
}
|
|
878
905
|
}
|
|
@@ -1791,7 +1818,7 @@ export async function handleCodexAuthAPI(
|
|
|
1791
1818
|
const loginOwner: CodexLoginStateRow = { status: "starting", startedAt: Date.now() };
|
|
1792
1819
|
codexAuthLoginState.set(flowId, loginOwner);
|
|
1793
1820
|
try {
|
|
1794
|
-
const { startLoginFlow, getLoginStatus } = await import("../oauth");
|
|
1821
|
+
const { startLoginFlow, getLoginStatus, publicOAuthAuthenticationErrorMessage } = await import("../oauth");
|
|
1795
1822
|
const result = await startLoginFlow("chatgpt", { forceLogin: true });
|
|
1796
1823
|
|
|
1797
1824
|
// Open the browser server-side (same pattern as /api/oauth/login in management-api.ts).
|
|
@@ -1993,7 +2020,13 @@ export async function handleCodexAuthAPI(
|
|
|
1993
2020
|
break;
|
|
1994
2021
|
}
|
|
1995
2022
|
if (st.done && st.error) {
|
|
1996
|
-
setCodexLoginState(flowId, {
|
|
2023
|
+
setCodexLoginState(flowId, {
|
|
2024
|
+
status: "error",
|
|
2025
|
+
// startLoginFlow projects background failures before storing login status, so
|
|
2026
|
+
// fixed actionable OAuth messages retain their type-derived remediation here.
|
|
2027
|
+
error: st.error,
|
|
2028
|
+
doneAt: Date.now(),
|
|
2029
|
+
});
|
|
1997
2030
|
completed = true;
|
|
1998
2031
|
break;
|
|
1999
2032
|
}
|
|
@@ -2011,7 +2044,7 @@ export async function handleCodexAuthAPI(
|
|
|
2011
2044
|
? "Configuration is busy; retry login shortly."
|
|
2012
2045
|
: error instanceof CodexCredentialRefreshBusyError || error instanceof CodexCredentialRefreshStaleError
|
|
2013
2046
|
? "Credential refresh is busy; retry login shortly."
|
|
2014
|
-
:
|
|
2047
|
+
: publicOAuthAuthenticationErrorMessage(error);
|
|
2015
2048
|
setCodexLoginState(flowId, {
|
|
2016
2049
|
status: "error",
|
|
2017
2050
|
error: message,
|
|
@@ -2028,7 +2061,7 @@ export async function handleCodexAuthAPI(
|
|
|
2028
2061
|
} catch (e) {
|
|
2029
2062
|
if (codexAuthLoginState.get(flowId) === loginOwner) codexAuthLoginState.delete(flowId);
|
|
2030
2063
|
const msg = e instanceof Error ? e.message : String(e);
|
|
2031
|
-
if (msg
|
|
2064
|
+
if (msg === "A login for chatgpt is already in progress") {
|
|
2032
2065
|
return jsonResponse({ error: msg, status: "pending" }, 409);
|
|
2033
2066
|
}
|
|
2034
2067
|
if (e instanceof CodexCredentialRefreshBusyError || e instanceof CodexCredentialRefreshStaleError) {
|
|
@@ -2036,7 +2069,8 @@ export async function handleCodexAuthAPI(
|
|
|
2036
2069
|
response.headers.set("Retry-After", "1");
|
|
2037
2070
|
return response;
|
|
2038
2071
|
}
|
|
2039
|
-
|
|
2072
|
+
const { publicOAuthAuthenticationErrorMessage } = await import("../oauth");
|
|
2073
|
+
return jsonResponse({ error: publicOAuthAuthenticationErrorMessage(e) }, 500);
|
|
2040
2074
|
}
|
|
2041
2075
|
}
|
|
2042
2076
|
|
|
@@ -12,7 +12,7 @@ import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, mo
|
|
|
12
12
|
import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider } from "../../generated/model-metadata";
|
|
13
13
|
import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
|
|
14
14
|
import { getProviderRegistryEntry } from "../../providers/registry";
|
|
15
|
-
import { applyProviderContextCap, providerContextCap } from "../../providers/context-cap";
|
|
15
|
+
import { applyProviderContextCap, providerContextCap, resolveUnknownRoutedContextWindow } from "../../providers/context-cap";
|
|
16
16
|
import { routedSlug, slugEquals, slugsEquivalent } from "../../providers/slug-codec";
|
|
17
17
|
import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
|
|
18
18
|
import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
|
|
@@ -122,11 +122,14 @@ export function applyCatalogModelMetadata(entry: RawEntry, model?: CatalogModel)
|
|
|
122
122
|
// so genuine upstream marketing names are preserved untouched.
|
|
123
123
|
const displayName = typeof model.displayName === "string" ? model.displayName.trim() : "";
|
|
124
124
|
if (displayName) entry.display_name = displayName;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
const resolvedContext = typeof model.contextWindow === "number" && model.contextWindow > 0
|
|
126
|
+
? model.contextWindow
|
|
127
|
+
: (model.contextCap !== undefined ? resolveUnknownRoutedContextWindow(model.contextCap) : undefined);
|
|
128
|
+
if (typeof resolvedContext === "number" && resolvedContext > 0) {
|
|
129
|
+
entry.context_window = resolvedContext;
|
|
130
|
+
entry.max_context_window = resolvedContext;
|
|
128
131
|
entry.auto_compact_token_limit = Math.min(
|
|
129
|
-
Math.floor(
|
|
132
|
+
Math.floor(resolvedContext * 0.9),
|
|
130
133
|
model.maxInputTokens ?? Number.POSITIVE_INFINITY,
|
|
131
134
|
);
|
|
132
135
|
}
|
|
@@ -33,13 +33,13 @@ import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, mo
|
|
|
33
33
|
import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider } from "../../generated/model-metadata";
|
|
34
34
|
import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive";
|
|
35
35
|
import {
|
|
36
|
-
|
|
36
|
+
captureFastPolicyAuthority,
|
|
37
37
|
serviceTierSupportForModel,
|
|
38
|
-
type CapturedServiceTierAdapterAuthority,
|
|
39
38
|
} from "../../providers/service-tier";
|
|
39
|
+
import type { FastPolicyAuthority } from "../../providers/fastwire";
|
|
40
40
|
import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport } from "../../providers/registry";
|
|
41
41
|
import { parseAntigravityAvailableModels, registerAntigravityDiscoveredWireModels } from "../../providers/antigravity-models";
|
|
42
|
-
import { applyProviderContextCap, providerContextCap } from "../../providers/context-cap";
|
|
42
|
+
import { applyProviderContextCap, providerContextCap, resolveUnknownRoutedContextWindow } from "../../providers/context-cap";
|
|
43
43
|
import { routedSlug, slugEquals, slugsEquivalent } from "../../providers/slug-codec";
|
|
44
44
|
import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity";
|
|
45
45
|
import { filterCursorConfiguredModelsByLiveDiscovery } from "../../adapters/cursor/discovery";
|
|
@@ -155,7 +155,7 @@ interface CapturedProviderGather {
|
|
|
155
155
|
readonly discovery: ResolvedProviderModelDiscovery;
|
|
156
156
|
readonly policy: CatalogProviderDiscoveryPolicySnapshot;
|
|
157
157
|
readonly request: CapturedModelsRequest;
|
|
158
|
-
readonly
|
|
158
|
+
readonly fastPolicyAuthority: FastPolicyAuthority;
|
|
159
159
|
readonly observedAuth?: ModelsAuthResolution;
|
|
160
160
|
/**
|
|
161
161
|
* Configured model ids this provider must keep even when live discovery omits
|
|
@@ -408,12 +408,12 @@ function captureProviderGather(
|
|
|
408
408
|
const enriched = detachedClone(withCanonicalOpenAiForwardAuthDefault(name, configured));
|
|
409
409
|
enrichProviderFromRegistry(name, enriched);
|
|
410
410
|
const registryTransportMatch = providerMatchesRegistryTransport(name, enriched);
|
|
411
|
-
const
|
|
411
|
+
const provider = recursivelyFreeze(enriched);
|
|
412
|
+
const fastPolicyAuthority = captureFastPolicyAuthority(
|
|
412
413
|
name,
|
|
413
|
-
|
|
414
|
+
provider,
|
|
414
415
|
registryTransportMatch,
|
|
415
416
|
);
|
|
416
|
-
const provider = recursivelyFreeze(enriched);
|
|
417
417
|
const observedAuth = authResolver.kind === "observed"
|
|
418
418
|
&& provider.authMode !== "forward"
|
|
419
419
|
&& provider.liveModels !== false
|
|
@@ -449,7 +449,7 @@ function captureProviderGather(
|
|
|
449
449
|
discovery,
|
|
450
450
|
policy,
|
|
451
451
|
request,
|
|
452
|
-
|
|
452
|
+
fastPolicyAuthority,
|
|
453
453
|
...(observedAuth ? { observedAuth: Object.freeze({ ...observedAuth }) } : {}),
|
|
454
454
|
...(retainConfiguredModelIds && retainConfiguredModelIds.size > 0
|
|
455
455
|
? { retainConfiguredModelIds }
|
|
@@ -518,7 +518,7 @@ function captureGatherFlight(
|
|
|
518
518
|
// It is the one member of a provider row that is legitimately a function,
|
|
519
519
|
// so it is dropped here rather than allowed to break every encode.
|
|
520
520
|
provider: omitProviderTransportExecutor(provider.provider),
|
|
521
|
-
|
|
521
|
+
fastPolicyAuthority: provider.fastPolicyAuthority,
|
|
522
522
|
// Combo retention is capture-time state, not a provider-row field. Two
|
|
523
523
|
// gathers that share providers but differ in combo targets must not join.
|
|
524
524
|
retainConfiguredModelIds: [...(provider.retainConfiguredModelIds ?? [])].sort(),
|
|
@@ -648,15 +648,16 @@ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig,
|
|
|
648
648
|
const supportsReasoningSummaries = configuredReasoningSummarySupport(prov, model.id);
|
|
649
649
|
const supportsServiceTier = serviceTierSupportForModel(prov, model.id, name);
|
|
650
650
|
const { supportsServiceTier: _staleServiceTier, ...modelWithoutServiceTier } = model;
|
|
651
|
+
// 已发现窗口只允许被配置值压低;缺窗口时,已开的 Context cap 就是实际窗口。
|
|
652
|
+
const discoveredWindow = typeof model.contextWindow === "number" && model.contextWindow > 0
|
|
653
|
+
? model.contextWindow
|
|
654
|
+
: undefined;
|
|
655
|
+
const hintedWindow = discoveredWindow !== undefined
|
|
656
|
+
? (configuredCap !== undefined ? Math.min(discoveredWindow, configuredCap) : discoveredWindow)
|
|
657
|
+
: (configuredCap ?? (providerCap !== undefined ? resolveUnknownRoutedContextWindow(providerCap) : undefined));
|
|
651
658
|
const hinted = {
|
|
652
659
|
...modelWithoutServiceTier,
|
|
653
|
-
...(
|
|
654
|
-
? {
|
|
655
|
-
contextWindow: typeof model.contextWindow === "number" && model.contextWindow > 0
|
|
656
|
-
? Math.min(model.contextWindow, configuredCap)
|
|
657
|
-
: configuredCap,
|
|
658
|
-
}
|
|
659
|
-
: {}),
|
|
660
|
+
...(hintedWindow !== undefined ? { contextWindow: hintedWindow } : {}),
|
|
660
661
|
...(inputModalities ? { inputModalities } : {}),
|
|
661
662
|
...(reasoningEfforts !== undefined ? { reasoningEfforts } : {}),
|
|
662
663
|
...(configuredMaxInput !== undefined
|
|
@@ -695,9 +696,11 @@ export function applyConfigHintsToCachedModels(name: string, prov: OcxProviderCo
|
|
|
695
696
|
|
|
696
697
|
|
|
697
698
|
/**
|
|
698
|
-
* Last-resort context window for combo member synthesis when discovery
|
|
699
|
-
* provider config
|
|
700
|
-
* `normalizeRoutedCatalogEntry` so incomplete live
|
|
699
|
+
* Last-resort context window for combo member synthesis when discovery,
|
|
700
|
+
* provider config, and an enabled Context cap all omit one. Matches the
|
|
701
|
+
* catalog entry default in `normalizeRoutedCatalogEntry` so incomplete live
|
|
702
|
+
* rows still catalog. An enabled Context cap is the operator-facing window,
|
|
703
|
+
* not a clamp on this placeholder.
|
|
701
704
|
*/
|
|
702
705
|
const COMBO_MEMBER_CONTEXT_FALLBACK = 128_000;
|
|
703
706
|
|
|
@@ -715,9 +718,9 @@ interface ComboCatalogMemberFallback {
|
|
|
715
718
|
* lacks a positive contextWindow, synthesize from the (registry-enriched)
|
|
716
719
|
* provider config so combos remain catalogued when targets are configured but
|
|
717
720
|
* discovery metadata is incomplete. Disabled providers stay unresolved.
|
|
718
|
-
* When hints still omit contextWindow, prefer known maxInputTokens, else
|
|
719
|
-
* COMBO_MEMBER_CONTEXT_FALLBACK so a live row
|
|
720
|
-
* whole combo from the public catalog.
|
|
721
|
+
* When hints still omit contextWindow, prefer known maxInputTokens, else the
|
|
722
|
+
* enabled Context cap, else COMBO_MEMBER_CONTEXT_FALLBACK so a live row
|
|
723
|
+
* without ctx does not drop the whole combo from the public catalog.
|
|
721
724
|
*/
|
|
722
725
|
export function resolveComboCatalogMember(
|
|
723
726
|
target: { provider: string; model: string },
|
|
@@ -806,12 +809,15 @@ export function resolveComboCatalogMember(
|
|
|
806
809
|
const uncappedContext = hintedContext
|
|
807
810
|
?? knownMaxInput
|
|
808
811
|
?? fallbackContext
|
|
809
|
-
?? (existing || prov ?
|
|
812
|
+
?? (existing || prov ? resolveUnknownRoutedContextWindow(contextCap) : undefined);
|
|
810
813
|
if (uncappedContext === undefined) return undefined;
|
|
811
|
-
|
|
812
|
-
const
|
|
814
|
+
// 真发现值才压低。resolveUnknownRoutedContextWindow 已经把 cap 当成窗口填进去了,不能再 min 一次。
|
|
815
|
+
const usedDiscoveredWindow = hintedContext !== undefined || knownMaxInput !== undefined || fallbackContext !== undefined;
|
|
816
|
+
const cappedContext = usedDiscoveredWindow
|
|
817
|
+
? applyProviderContextCap(uncappedContext, contextCap)
|
|
818
|
+
: uncappedContext;
|
|
813
819
|
const contextWindow = cappedContext ?? uncappedContext;
|
|
814
|
-
const fallbackCapped =
|
|
820
|
+
const fallbackCapped = usedDiscoveredWindow
|
|
815
821
|
&& contextCap !== undefined
|
|
816
822
|
&& cappedContext !== undefined
|
|
817
823
|
&& cappedContext !== uncappedContext;
|
|
@@ -1208,7 +1214,13 @@ async function fetchProviderModelsWithAuth(
|
|
|
1208
1214
|
"degraded",
|
|
1209
1215
|
);
|
|
1210
1216
|
}
|
|
1211
|
-
const
|
|
1217
|
+
const cursorFetch = (prov as OcxProviderConfig & { fetch?: typeof globalThis.fetch }).fetch;
|
|
1218
|
+
const liveResult = await fetchCursorUsableModels({
|
|
1219
|
+
apiKey,
|
|
1220
|
+
baseUrl: prov.baseUrl,
|
|
1221
|
+
upstreamHttpVersion: prov.upstreamHttpVersion,
|
|
1222
|
+
...(cursorFetch ? { fetch: cursorFetch } : {}),
|
|
1223
|
+
});
|
|
1212
1224
|
if (liveResult.ok) {
|
|
1213
1225
|
const available = filterCursorConfiguredModelsByLiveDiscovery(configured, liveResult.models);
|
|
1214
1226
|
const result = available.length > 0 ? available : configured;
|
|
@@ -307,8 +307,8 @@ export function deriveEntry(
|
|
|
307
307
|
if (isRouted) {
|
|
308
308
|
// A routed model is NOT the native template: never inherit its context
|
|
309
309
|
// window when /models omits context metadata (#992). Known metadata
|
|
310
|
-
// restores exact values below;
|
|
311
|
-
// supplies the
|
|
310
|
+
// restores exact values below; an enabled Context cap fills the gap;
|
|
311
|
+
// otherwise the strict-fields fallback supplies the 128k triple.
|
|
312
312
|
if (!codexForwardNativeCapabilityAlias) {
|
|
313
313
|
delete e.context_window;
|
|
314
314
|
delete e.max_context_window;
|
|
@@ -1214,6 +1214,20 @@ interface RetainedCatalogSyncResult {
|
|
|
1214
1214
|
skippedReason?: "desired_disabled";
|
|
1215
1215
|
}
|
|
1216
1216
|
|
|
1217
|
+
/**
|
|
1218
|
+
* Catalog/cache commit overrides.
|
|
1219
|
+
*
|
|
1220
|
+
* An explicit `ocx sync` is also the refresh path for side profiles that consume
|
|
1221
|
+
* the OpenCodex catalog without injection (for example a custom `model_provider`
|
|
1222
|
+
* that routes to the proxy). In that mode the Codex integration toggle only
|
|
1223
|
+
* governs config/history injection; the catalog and models cache may still be
|
|
1224
|
+
* refreshed, so `allowWhenDesiredDisabled` lets the commit path ignore the OFF
|
|
1225
|
+
* gate that otherwise protects a fully native home.
|
|
1226
|
+
*/
|
|
1227
|
+
export interface CodexCatalogSyncOptions {
|
|
1228
|
+
allowWhenDesiredDisabled?: boolean;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1217
1231
|
interface RetainedCatalogSyncWrite {
|
|
1218
1232
|
readonly config: OcxConfig;
|
|
1219
1233
|
readonly goModels: CatalogModel[];
|
|
@@ -1618,7 +1632,10 @@ function currentDisabledModelsForRestore(): Set<string> | null {
|
|
|
1618
1632
|
}
|
|
1619
1633
|
}
|
|
1620
1634
|
|
|
1621
|
-
export async function syncCatalogModels(
|
|
1635
|
+
export async function syncCatalogModels(
|
|
1636
|
+
config: OcxConfig,
|
|
1637
|
+
options?: CodexCatalogSyncOptions,
|
|
1638
|
+
): Promise<RetainedCatalogSyncResult> {
|
|
1622
1639
|
const owningCodexHome = getCodexHome();
|
|
1623
1640
|
const preflightRead = readRetainedCatalogSync(config);
|
|
1624
1641
|
if (preflightRead === null) {
|
|
@@ -1654,8 +1671,10 @@ export async function syncCatalogModels(config: OcxConfig): Promise<RetainedCata
|
|
|
1654
1671
|
// evidence revalidation below cannot see that — intent lives in our config,
|
|
1655
1672
|
// not in the catalog files — so the policy is re-read here, under K, right
|
|
1656
1673
|
// before the only write. A lost race becomes the discriminated skip instead
|
|
1657
|
-
// of a routed catalog/cache surviving a completed disable.
|
|
1658
|
-
|
|
1674
|
+
// of a routed catalog/cache surviving a completed disable. An explicit
|
|
1675
|
+
// catalog-only sync opts out of that gate: the user asked for a refresh even
|
|
1676
|
+
// when injection is OFF, and the toggle only protects config/history writes.
|
|
1677
|
+
if (!shouldSyncCodexOnStart(loadConfig()) && options?.allowWhenDesiredDisabled !== true) {
|
|
1659
1678
|
return {
|
|
1660
1679
|
added: 0,
|
|
1661
1680
|
path: prepared.catalogPath,
|
|
@@ -1755,13 +1774,16 @@ export function restoreCodexCatalog(): { removed: number; kept: number; path: st
|
|
|
1755
1774
|
export function invalidateCodexModelsCacheWithPermit(
|
|
1756
1775
|
permit: CatalogWritePermit,
|
|
1757
1776
|
owningCodexHome: string,
|
|
1777
|
+
options?: CodexCatalogSyncOptions,
|
|
1758
1778
|
): boolean {
|
|
1759
1779
|
try {
|
|
1760
1780
|
// This permit is a REACQUISITION: refreshCodexModelCatalog's commit released
|
|
1761
1781
|
// K before this rewrite runs, so the commit-path desired-state check cannot
|
|
1762
1782
|
// cover it. A disable landing in that gap must not be overwritten by a
|
|
1763
1783
|
// routed cache write — re-read intent under this permit, same as the commit.
|
|
1764
|
-
|
|
1784
|
+
// The catalog-only sync override applies here too so an explicit refresh
|
|
1785
|
+
// keeps the cache consistent with the catalog it just wrote.
|
|
1786
|
+
if (!shouldSyncCodexOnStart(loadConfig()) && options?.allowWhenDesiredDisabled !== true) return false;
|
|
1765
1787
|
const catalogPath = readCodexCatalogPath();
|
|
1766
1788
|
if (!existsSync(catalogPath)) return false;
|
|
1767
1789
|
const catalog = JSON.parse(readFileSync(catalogPath, "utf8"));
|
|
@@ -1803,11 +1825,11 @@ export function invalidateCodexModelsCacheWithPermit(
|
|
|
1803
1825
|
}
|
|
1804
1826
|
}
|
|
1805
1827
|
|
|
1806
|
-
export function invalidateCodexModelsCache(): boolean {
|
|
1828
|
+
export function invalidateCodexModelsCache(options?: CodexCatalogSyncOptions): boolean {
|
|
1807
1829
|
const owningCodexHome = getCodexHome();
|
|
1808
1830
|
const outcome = withCatalogWriteSerialization(
|
|
1809
1831
|
owningCodexHome,
|
|
1810
|
-
permit => invalidateCodexModelsCacheWithPermit(permit, owningCodexHome),
|
|
1832
|
+
permit => invalidateCodexModelsCacheWithPermit(permit, owningCodexHome, options),
|
|
1811
1833
|
);
|
|
1812
1834
|
return outcome.kind === "completed" && outcome.value;
|
|
1813
1835
|
}
|