@lyogvce/dsh-codex-subscription 0.2.4 → 0.2.6
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/README.md +2 -2
- package/lib/client.js +1 -0
- package/lib/index.d.ts +30 -25
- package/lib/index.js +149 -90
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
- 每次请求前检查 `access_token` 的 JWT `exp`:距离过期不足 5 分钟时自动通过单飞锁(Single-flight)向 `https://auth.openai.com/oauth/token` 刷新,并通过 `realpathSync` + 临时文件 `rename` 原子写回 `auth.json`,保证与 Codex CLI 永不互踢下线。
|
|
14
14
|
2. **零网络轮询的本地模型目录发现、防降级保护与可观测性日志**
|
|
15
15
|
- 直接读取 `~/.codex/models_cache.json`(每次按文件 `mtime` 与大小做微秒级 `stat` 校验,仅在文件变化时重读解析),提取 `visibility === "list"` 的模型及其 `context_window`、`supported_reasoning_levels`(自动将 `ultra` 映射为 DSH 的 `max` 档位)与 `default_verbosity`。
|
|
16
|
-
- **防旧版客户端降级污染**:自动将高版本 `models_cache.json` 备份至 `~/.codex
|
|
17
|
-
- **可观测性日志**:自动在 `~/.codex/dsh-codex-subscription.log`(带 512KB
|
|
16
|
+
- **防旧版客户端降级污染**:自动将高版本 `models_cache.json` 备份至 `~/.codex/cache/dsh-codex-subscription/models-cache-backup.json`;当外部旧版进程(如 `dsh-subagent-codex` 内置的 `0.153.4`)将 `models_cache.json` 降级覆写为不含新模型的旧缓存时,自动拦截降级并保留高版本模型目录。
|
|
17
|
+
- **可观测性日志**:自动在 `~/.codex/cache/dsh-codex-subscription/plugin.log`(带 512KB 自动滚动截断,忽略纯 `fetched_at` 心跳刷新)记录目录加载(`catalog.load` / `catalog.snapshot`)、降级拦截(`catalog.downgrade_rejected`)、备份恢复(`catalog.backup_restored`)与凭证续期(`auth.refreshed` / `auth.refresh_failed`)事件。
|
|
18
18
|
3. **支持按配置屏蔽模型(`disabledModels`)与自定义上下文窗口(`contextWindows`)**
|
|
19
19
|
- 在插件配置中通过 `disabledModels` 屏蔽指定模型 `slug`,保持模型选择器干净清爽。
|
|
20
20
|
- 通过 `contextWindows` 按模型 `slug` 自定义向 DSH 会话与自动压缩器暴露的预算窗口(例如设为 `400000`);未配置时默认使用模型物理上限 `max_context_window`(`872000`)。同时底层 `PiCodexModel.contextWindow` 始终保持物理上限 `872000`,彻底防止 `dsh-llm-pi-ai` 在预算窗口与物理上限之间误判 `CONTEXT_WINDOW_EXCEEDED`。
|
package/lib/client.js
CHANGED
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
import { PiCodexModel } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
2
|
+
//#region src/host-types.d.ts
|
|
3
|
+
interface LlmServiceLike {
|
|
4
|
+
registerAdapter: (providers: string[], adapter: unknown) => (() => void) & {
|
|
5
|
+
replace?: (next: string[]) => void;
|
|
6
|
+
};
|
|
7
|
+
emitAdaptersUpdated?: () => void;
|
|
8
|
+
}
|
|
9
|
+
type PluginLogLevel = 'INFO' | 'WARN' | 'ERROR';
|
|
10
|
+
type PluginLogFn = (level: PluginLogLevel, event: string, detail: Record<string, unknown>) => void;
|
|
11
|
+
interface LoggerLike {
|
|
12
|
+
info?: (message: string) => void;
|
|
13
|
+
warn?: (message: string) => void;
|
|
14
|
+
error?: (message: string) => void;
|
|
15
|
+
}
|
|
16
|
+
interface PluginContextLike {
|
|
17
|
+
llm: LlmServiceLike;
|
|
18
|
+
logger?: LoggerLike | undefined;
|
|
19
|
+
get?: (name: string) => unknown;
|
|
20
|
+
on?: (event: string, listener: () => void) => () => void;
|
|
21
|
+
effect?: (factory: () => void | (() => void), label?: string) => () => void;
|
|
22
|
+
inject?: (services: readonly string[], callback: (ctx: PluginContextLike) => void) => () => void;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
2
25
|
//#region src/auth.d.ts
|
|
3
26
|
interface CodexAuthTokens {
|
|
4
27
|
access_token?: string;
|
|
@@ -19,7 +42,7 @@ interface JwtClaims {
|
|
|
19
42
|
interface AuthManagerOptions {
|
|
20
43
|
getCodexHome: () => string;
|
|
21
44
|
getClientVersion?: (() => string) | undefined;
|
|
22
|
-
onLog?:
|
|
45
|
+
onLog?: PluginLogFn | undefined;
|
|
23
46
|
fetchImpl?: typeof fetch | undefined;
|
|
24
47
|
now?: (() => number) | undefined;
|
|
25
48
|
}
|
|
@@ -36,6 +59,8 @@ declare function createAuthManager(options: AuthManagerOptions): {
|
|
|
36
59
|
};
|
|
37
60
|
//#endregion
|
|
38
61
|
//#region src/catalog.d.ts
|
|
62
|
+
export declare const PLUGIN_CACHE_DIR_NAME = "cache/dsh-codex-subscription";
|
|
63
|
+
export declare const BACKUP_CACHE_FILE_NAME = "models-cache-backup.json";
|
|
39
64
|
interface RawReasoningLevel {
|
|
40
65
|
effort?: string;
|
|
41
66
|
description?: string;
|
|
@@ -59,6 +84,7 @@ interface RawModelsCache {
|
|
|
59
84
|
client_version?: string;
|
|
60
85
|
models?: RawCodexModelEntry[];
|
|
61
86
|
}
|
|
87
|
+
declare function parseRawModelsCache(rawText: string): RawModelsCache;
|
|
62
88
|
declare function compareClientVersions(a: string | undefined, b: string | undefined): number;
|
|
63
89
|
interface CodexModelMetadata {
|
|
64
90
|
readonly contextWindow: number;
|
|
@@ -115,32 +141,10 @@ declare function createUsageFetcher(options: UsageFetcherOptions): {
|
|
|
115
141
|
invalidate: () => void;
|
|
116
142
|
};
|
|
117
143
|
//#endregion
|
|
118
|
-
//#region src/host-types.d.ts
|
|
119
|
-
interface LlmServiceLike {
|
|
120
|
-
registerAdapter: (providers: string[], adapter: unknown) => (() => void) & {
|
|
121
|
-
replace?: (next: string[]) => void;
|
|
122
|
-
};
|
|
123
|
-
emitAdaptersUpdated?: () => void;
|
|
124
|
-
}
|
|
125
|
-
interface LoggerLike {
|
|
126
|
-
info?: (message: string) => void;
|
|
127
|
-
warn?: (message: string) => void;
|
|
128
|
-
error?: (message: string) => void;
|
|
129
|
-
}
|
|
130
|
-
interface PluginContextLike {
|
|
131
|
-
llm: LlmServiceLike;
|
|
132
|
-
logger?: LoggerLike | undefined;
|
|
133
|
-
get?: (name: string) => unknown;
|
|
134
|
-
on?: (event: string, listener: () => void) => () => void;
|
|
135
|
-
effect?: (factory: () => void | (() => void), label?: string) => () => void;
|
|
136
|
-
inject?: (services: readonly string[], callback: (ctx: PluginContextLike) => void) => () => void;
|
|
137
|
-
}
|
|
138
|
-
//#endregion
|
|
139
144
|
//#region src/index.d.ts
|
|
140
145
|
export declare const name = "codex-subscription";
|
|
141
146
|
export declare const inject: string[];
|
|
142
|
-
export declare const LOG_FILE_NAME = "
|
|
143
|
-
export declare const BACKUP_CACHE_FILE_NAME = ".dsh-models-cache-backup.json";
|
|
147
|
+
export declare const LOG_FILE_NAME = "plugin.log";
|
|
144
148
|
export declare const Config: any;
|
|
145
149
|
export declare function apply(ctx: PluginContextLike, config?: Record<string, unknown>): void;
|
|
146
150
|
export declare const __test: Readonly<{
|
|
@@ -159,6 +163,7 @@ export declare const __test: Readonly<{
|
|
|
159
163
|
buildThinkingLevelMap: typeof buildThinkingLevelMap;
|
|
160
164
|
compareClientVersions: typeof compareClientVersions;
|
|
161
165
|
parseModelsCache: typeof parseModelsCache;
|
|
166
|
+
parseRawModelsCache: typeof parseRawModelsCache;
|
|
162
167
|
sanitizeCodexPayload: typeof sanitizeCodexPayload;
|
|
163
168
|
buildCodexHeaders: typeof buildCodexHeaders;
|
|
164
169
|
buildCodexUserAgent: typeof buildCodexUserAgent;
|
|
@@ -167,4 +172,4 @@ export declare const __test: Readonly<{
|
|
|
167
172
|
createUsageFetcher: typeof createUsageFetcher;
|
|
168
173
|
}>;
|
|
169
174
|
//#endregion
|
|
170
|
-
export type { CodexModelMetadata, CodexQuotaResponse, CodexQuotaSnapshot, CodexQuotaWindow, CodexResolvedCredentials, ParsedModelsCatalog, PluginContextLike, RawModelsCache };
|
|
175
|
+
export type { CodexModelMetadata, CodexQuotaResponse, CodexQuotaSnapshot, CodexQuotaWindow, CodexResolvedCredentials, ParsedModelsCatalog, PluginContextLike, PluginLogLevel, RawModelsCache };
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { appendFileSync, chmodSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
1
|
+
import { appendFileSync, chmodSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
4
|
import { LlmError, resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
5
5
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
@@ -13,6 +13,8 @@ const PROVIDER = "openai-codex";
|
|
|
13
13
|
const DISPLAY_NAME = "Codex";
|
|
14
14
|
const DEFAULT_CLIENT_VERSION = "0.163.0";
|
|
15
15
|
const DEFAULT_CONTEXT_WINDOW = 872e3;
|
|
16
|
+
const PLUGIN_CACHE_DIR_NAME = "cache/dsh-codex-subscription";
|
|
17
|
+
const BACKUP_CACHE_FILE_NAME = "models-cache-backup.json";
|
|
16
18
|
const THINKING_LEVELS = [
|
|
17
19
|
"off",
|
|
18
20
|
"minimal",
|
|
@@ -29,6 +31,17 @@ const DEFAULT_DISABLED_MODELS = Object.freeze([
|
|
|
29
31
|
"gpt-5.6-terra",
|
|
30
32
|
"gpt-5.6-luna"
|
|
31
33
|
]);
|
|
34
|
+
function parseRawModelsCache(rawText) {
|
|
35
|
+
const parsed = JSON.parse(rawText);
|
|
36
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new SyntaxError("models_cache.json root must be a JSON object");
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
function isSameModelsCatalogPayload(a, b) {
|
|
40
|
+
if (!a || !b) return false;
|
|
41
|
+
const { fetched_at: _fa, ...restA } = a;
|
|
42
|
+
const { fetched_at: _fb, ...restB } = b;
|
|
43
|
+
return JSON.stringify(restA) === JSON.stringify(restB);
|
|
44
|
+
}
|
|
32
45
|
function compareClientVersions(a, b) {
|
|
33
46
|
const parseParts = (ver) => typeof ver === "string" && ver.trim() !== "" ? ver.trim().split(/[^0-9]+/u).filter(Boolean).map((n) => Number.parseInt(n, 10)) : [];
|
|
34
47
|
const pa = parseParts(a);
|
|
@@ -362,11 +375,39 @@ function extractAccountId(parsed, accessToken) {
|
|
|
362
375
|
if (typeof jwtAccountId === "string" && jwtAccountId.trim() !== "") return jwtAccountId.trim();
|
|
363
376
|
}
|
|
364
377
|
}
|
|
365
|
-
|
|
366
|
-
let targetPath = filePath;
|
|
378
|
+
function resolveRealTargetPath(filePath) {
|
|
367
379
|
try {
|
|
368
|
-
|
|
369
|
-
} catch {
|
|
380
|
+
return realpathSync(filePath);
|
|
381
|
+
} catch {
|
|
382
|
+
try {
|
|
383
|
+
return join(realpathSync(dirname(filePath)), basename(filePath));
|
|
384
|
+
} catch {
|
|
385
|
+
return filePath;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function writeFileAtomicSync(filePath, content) {
|
|
390
|
+
mkdirSync(dirname(filePath), {
|
|
391
|
+
recursive: true,
|
|
392
|
+
mode: 448
|
|
393
|
+
});
|
|
394
|
+
const targetPath = resolveRealTargetPath(filePath);
|
|
395
|
+
const tempPath = join(dirname(targetPath), `.${basename(targetPath)}.tmp-${process.pid}-${randomUUID()}`);
|
|
396
|
+
try {
|
|
397
|
+
writeFileSync(tempPath, content, { mode: 384 });
|
|
398
|
+
try {
|
|
399
|
+
chmodSync(tempPath, 384);
|
|
400
|
+
} catch {}
|
|
401
|
+
renameSync(tempPath, targetPath);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
try {
|
|
404
|
+
unlinkSync(tempPath);
|
|
405
|
+
} catch {}
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async function writeJsonAtomic(filePath, data) {
|
|
410
|
+
const targetPath = resolveRealTargetPath(filePath);
|
|
370
411
|
const tempPath = join(dirname(targetPath), `.auth.json.tmp-${process.pid}-${randomUUID()}`);
|
|
371
412
|
const content = `${JSON.stringify(data, null, 2)}\n`;
|
|
372
413
|
await writeFile(tempPath, content, {
|
|
@@ -431,25 +472,34 @@ function createAuthManager(options) {
|
|
|
431
472
|
return exp * 1e3 - nowFn() > TOKEN_REFRESH_LEAD_MS;
|
|
432
473
|
}
|
|
433
474
|
async function refreshToken(authPath, rawAuth) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
475
|
+
let fresh;
|
|
476
|
+
try {
|
|
477
|
+
const refreshTok = rawAuth?.tokens?.refresh_token;
|
|
478
|
+
if (typeof refreshTok !== "string" || refreshTok.trim() === "") throw new LlmError("Codex 凭证已过期且缺少 refresh_token,请重新运行 codex login", "AUTH_FAILED");
|
|
479
|
+
const response = await fetchFn(CODEX_TOKEN_URL, {
|
|
480
|
+
method: "POST",
|
|
481
|
+
headers: buildCodexHeaders({
|
|
482
|
+
"Content-Type": "application/json",
|
|
483
|
+
Accept: "application/json"
|
|
484
|
+
}, options.getClientVersion?.()),
|
|
485
|
+
body: JSON.stringify({
|
|
486
|
+
client_id: CODEX_CLIENT_ID,
|
|
487
|
+
grant_type: "refresh_token",
|
|
488
|
+
refresh_token: refreshTok,
|
|
489
|
+
scope: "openid profile email"
|
|
490
|
+
}),
|
|
491
|
+
signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS)
|
|
492
|
+
});
|
|
493
|
+
if (!response.ok) throw new LlmError(`Codex Token 刷新失败 (HTTP ${response.status}),请重新运行 codex login`, "AUTH_FAILED");
|
|
494
|
+
fresh = await response.json();
|
|
495
|
+
if (typeof fresh?.access_token !== "string" || fresh.access_token.trim() === "") throw new LlmError("Codex Token 刷新响应缺少 access_token", "AUTH_FAILED");
|
|
496
|
+
} catch (error) {
|
|
497
|
+
options.onLog?.("ERROR", "auth.refresh_failed", {
|
|
498
|
+
authPath,
|
|
499
|
+
error: safeErrorMessage(error)
|
|
500
|
+
});
|
|
501
|
+
throw error;
|
|
502
|
+
}
|
|
453
503
|
const nextAuth = {
|
|
454
504
|
...rawAuth,
|
|
455
505
|
tokens: {
|
|
@@ -513,13 +563,15 @@ function createAuthManager(options) {
|
|
|
513
563
|
//#region src/index.ts
|
|
514
564
|
const name = "codex-subscription";
|
|
515
565
|
const inject = ["llm"];
|
|
516
|
-
const LOG_FILE_NAME = "
|
|
517
|
-
const BACKUP_CACHE_FILE_NAME = ".dsh-models-cache-backup.json";
|
|
566
|
+
const LOG_FILE_NAME = "plugin.log";
|
|
518
567
|
const STREAM_IDLE_TIMEOUT_MS = 3e5;
|
|
519
568
|
const MAX_REQUEST_IMAGE_BYTES = 20971520;
|
|
520
569
|
const REQUEST_IMAGE_PIXEL_BUDGET = 4194304;
|
|
521
570
|
const REQUEST_IMAGE_MAX_BYTES = 1048576;
|
|
522
571
|
const MAX_LOG_FILE_BYTES = 524288;
|
|
572
|
+
function resolvePluginCacheDir(codexHome) {
|
|
573
|
+
return join(codexHome, PLUGIN_CACHE_DIR_NAME);
|
|
574
|
+
}
|
|
523
575
|
function appendPluginLog(codexHome, ctx, level, event, detail) {
|
|
524
576
|
const line = `[${new Date().toISOString()}] [${level}] ${event} ${JSON.stringify(detail)}\n`;
|
|
525
577
|
if (level === "ERROR") {
|
|
@@ -529,14 +581,18 @@ function appendPluginLog(codexHome, ctx, level, event, detail) {
|
|
|
529
581
|
if (typeof ctx.logger?.warn === "function") ctx.logger.warn(`[dsh-codex-subscription] ${event}: ${JSON.stringify(detail)}`);
|
|
530
582
|
}
|
|
531
583
|
try {
|
|
532
|
-
const
|
|
584
|
+
const cacheDir = resolvePluginCacheDir(codexHome);
|
|
585
|
+
mkdirSync(cacheDir, {
|
|
586
|
+
recursive: true,
|
|
587
|
+
mode: 448
|
|
588
|
+
});
|
|
589
|
+
const logPath = join(cacheDir, LOG_FILE_NAME);
|
|
533
590
|
try {
|
|
534
591
|
if (statSync(logPath).size > MAX_LOG_FILE_BYTES) {
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
});
|
|
592
|
+
const buf = readFileSync(logPath);
|
|
593
|
+
const halfOffset = Math.max(0, buf.length - Math.floor(MAX_LOG_FILE_BYTES / 2));
|
|
594
|
+
const newlineIdx = buf.indexOf(10, halfOffset);
|
|
595
|
+
writeFileAtomicSync(logPath, buf.subarray(newlineIdx >= 0 && newlineIdx + 1 < buf.length ? newlineIdx + 1 : halfOffset));
|
|
540
596
|
}
|
|
541
597
|
} catch {}
|
|
542
598
|
appendFileSync(logPath, line, {
|
|
@@ -547,19 +603,14 @@ function appendPluginLog(codexHome, ctx, level, event, detail) {
|
|
|
547
603
|
}
|
|
548
604
|
function readBackupCache(codexHome) {
|
|
549
605
|
try {
|
|
550
|
-
return
|
|
606
|
+
return parseRawModelsCache(readFileSync(join(resolvePluginCacheDir(codexHome), BACKUP_CACHE_FILE_NAME), "utf8"));
|
|
551
607
|
} catch {
|
|
552
608
|
return;
|
|
553
609
|
}
|
|
554
610
|
}
|
|
555
611
|
function writeBackupCache(codexHome, raw) {
|
|
556
612
|
try {
|
|
557
|
-
|
|
558
|
-
if (existing && compareClientVersions(raw.client_version, existing.client_version) < 0) return;
|
|
559
|
-
writeFileSync(join(codexHome, BACKUP_CACHE_FILE_NAME), `${JSON.stringify(raw, null, 2)}\n`, {
|
|
560
|
-
encoding: "utf8",
|
|
561
|
-
mode: 384
|
|
562
|
-
});
|
|
613
|
+
writeFileAtomicSync(join(resolvePluginCacheDir(codexHome), BACKUP_CACHE_FILE_NAME), `${JSON.stringify(raw, null, 2)}\n`);
|
|
563
614
|
} catch {}
|
|
564
615
|
}
|
|
565
616
|
const Config = z.object({
|
|
@@ -609,7 +660,7 @@ function apply(ctx, config = {}) {
|
|
|
609
660
|
});
|
|
610
661
|
let fileSignature;
|
|
611
662
|
let cachedRaw;
|
|
612
|
-
let
|
|
663
|
+
let rawRevision = 0;
|
|
613
664
|
let snapshotSignature;
|
|
614
665
|
let currentCatalog = {
|
|
615
666
|
clientVersion: DEFAULT_CLIENT_VERSION,
|
|
@@ -670,64 +721,71 @@ function apply(ctx, config = {}) {
|
|
|
670
721
|
const cachePath = join(codexHome, "models_cache.json");
|
|
671
722
|
const disabled = getDisabledModels();
|
|
672
723
|
const contextWindows = getContextWindows();
|
|
724
|
+
const setActiveRaw = (next) => {
|
|
725
|
+
cachedRaw = next;
|
|
726
|
+
rawRevision += 1;
|
|
727
|
+
};
|
|
728
|
+
const tryRestoreFromBackup = (extra, minVersion) => {
|
|
729
|
+
const backupRaw = readBackupCache(codexHome);
|
|
730
|
+
if (backupRaw === void 0 || minVersion !== void 0 && compareClientVersions(backupRaw.client_version, minVersion) <= 0) return false;
|
|
731
|
+
setActiveRaw(backupRaw);
|
|
732
|
+
appendPluginLog(codexHome, ctx, "WARN", "catalog.backup_restored", {
|
|
733
|
+
cachePath,
|
|
734
|
+
...extra,
|
|
735
|
+
restoredVersion: backupRaw.client_version ?? "0.163.0",
|
|
736
|
+
restoredFetchedAt: backupRaw.fetched_at ?? null
|
|
737
|
+
});
|
|
738
|
+
return true;
|
|
739
|
+
};
|
|
740
|
+
const handleReadError = (error) => {
|
|
741
|
+
if (cachedRaw === void 0) tryRestoreFromBackup({ reason: safeErrorMessage(error) });
|
|
742
|
+
appendPluginLog(codexHome, ctx, cachedRaw === void 0 ? "ERROR" : "WARN", "catalog.read_error", {
|
|
743
|
+
cachePath,
|
|
744
|
+
hasFallbackSnapshot: cachedRaw !== void 0,
|
|
745
|
+
error: safeErrorMessage(error)
|
|
746
|
+
});
|
|
747
|
+
};
|
|
748
|
+
let stat;
|
|
673
749
|
try {
|
|
674
|
-
|
|
750
|
+
stat = statSync(cachePath);
|
|
751
|
+
} catch (error) {
|
|
752
|
+
const missingSig = `missing:${cachePath}:${error?.code ?? "ERR"}`;
|
|
753
|
+
if (missingSig !== fileSignature) {
|
|
754
|
+
fileSignature = missingSig;
|
|
755
|
+
handleReadError(error);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (stat !== void 0) {
|
|
675
759
|
const nextFileSig = `${cachePath}:${stat.mtimeMs}:${stat.size}`;
|
|
676
760
|
if (nextFileSig !== fileSignature) {
|
|
677
|
-
const incomingRaw = JSON.parse(readFileSync(cachePath, "utf8"));
|
|
678
761
|
fileSignature = nextFileSig;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
keptVersion: cachedRaw.client_version ?? "0.163.0",
|
|
683
|
-
rejectedVersion: incomingRaw.client_version ?? "unknown",
|
|
684
|
-
incomingFetchedAt: incomingRaw.fetched_at ?? null
|
|
685
|
-
});
|
|
686
|
-
else {
|
|
687
|
-
const backupRaw = readBackupCache(codexHome);
|
|
688
|
-
if (backupRaw !== void 0 && compareClientVersions(backupRaw.client_version, incomingRaw.client_version) > 0) {
|
|
689
|
-
cachedRaw = backupRaw;
|
|
690
|
-
appendPluginLog(codexHome, ctx, "WARN", "catalog.backup_restored", {
|
|
691
|
-
cachePath,
|
|
692
|
-
diskVersion: incomingRaw.client_version ?? "unknown",
|
|
693
|
-
restoredVersion: backupRaw.client_version ?? "0.163.0",
|
|
694
|
-
restoredFetchedAt: backupRaw.fetched_at ?? null
|
|
695
|
-
});
|
|
696
|
-
} else {
|
|
697
|
-
cachedRaw = incomingRaw;
|
|
698
|
-
writeBackupCache(codexHome, incomingRaw);
|
|
699
|
-
appendPluginLog(codexHome, ctx, "INFO", "catalog.load", {
|
|
700
|
-
cachePath,
|
|
701
|
-
clientVersion: incomingRaw.client_version ?? "0.163.0",
|
|
702
|
-
fetchedAt: incomingRaw.fetched_at ?? null,
|
|
703
|
-
rawModelCount: Array.isArray(incomingRaw.models) ? incomingRaw.models.length : 0
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
} catch (error) {
|
|
709
|
-
if (cachedRaw === void 0) {
|
|
710
|
-
const backupRaw = readBackupCache(codexHome);
|
|
711
|
-
if (backupRaw !== void 0) {
|
|
712
|
-
cachedRaw = backupRaw;
|
|
713
|
-
appendPluginLog(codexHome, ctx, "WARN", "catalog.backup_restored", {
|
|
762
|
+
try {
|
|
763
|
+
const incomingRaw = parseRawModelsCache(readFileSync(cachePath, "utf8"));
|
|
764
|
+
if (cachedRaw !== void 0 && compareClientVersions(incomingRaw.client_version, cachedRaw.client_version) < 0) appendPluginLog(codexHome, ctx, "WARN", "catalog.downgrade_rejected", {
|
|
714
765
|
cachePath,
|
|
715
|
-
|
|
716
|
-
|
|
766
|
+
keptVersion: cachedRaw.client_version ?? "0.163.0",
|
|
767
|
+
rejectedVersion: incomingRaw.client_version ?? "unknown",
|
|
768
|
+
incomingFetchedAt: incomingRaw.fetched_at ?? null
|
|
717
769
|
});
|
|
770
|
+
else if (cachedRaw !== void 0 || !tryRestoreFromBackup({ diskVersion: incomingRaw.client_version ?? "unknown" }, incomingRaw.client_version)) {
|
|
771
|
+
if (!isSameModelsCatalogPayload(cachedRaw, incomingRaw)) {
|
|
772
|
+
setActiveRaw(incomingRaw);
|
|
773
|
+
writeBackupCache(codexHome, incomingRaw);
|
|
774
|
+
appendPluginLog(codexHome, ctx, "INFO", "catalog.load", {
|
|
775
|
+
cachePath,
|
|
776
|
+
clientVersion: incomingRaw.client_version ?? "0.163.0",
|
|
777
|
+
fetchedAt: incomingRaw.fetched_at ?? null,
|
|
778
|
+
rawModelCount: Array.isArray(incomingRaw.models) ? incomingRaw.models.length : 0
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
} catch (error) {
|
|
783
|
+
handleReadError(error);
|
|
718
784
|
}
|
|
719
785
|
}
|
|
720
|
-
if (!warnedMissingFile) {
|
|
721
|
-
warnedMissingFile = true;
|
|
722
|
-
appendPluginLog(codexHome, ctx, cachedRaw === void 0 ? "ERROR" : "WARN", "catalog.read_error", {
|
|
723
|
-
cachePath,
|
|
724
|
-
hasFallbackSnapshot: cachedRaw !== void 0,
|
|
725
|
-
error: safeErrorMessage(error)
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
786
|
}
|
|
729
787
|
const nextSnapshotSig = JSON.stringify([
|
|
730
|
-
|
|
788
|
+
rawRevision,
|
|
731
789
|
disabled,
|
|
732
790
|
contextWindows
|
|
733
791
|
]);
|
|
@@ -833,6 +891,7 @@ const __test = Object.freeze({
|
|
|
833
891
|
buildThinkingLevelMap,
|
|
834
892
|
compareClientVersions,
|
|
835
893
|
parseModelsCache,
|
|
894
|
+
parseRawModelsCache,
|
|
836
895
|
sanitizeCodexPayload,
|
|
837
896
|
buildCodexHeaders,
|
|
838
897
|
buildCodexUserAgent,
|
|
@@ -841,4 +900,4 @@ const __test = Object.freeze({
|
|
|
841
900
|
createUsageFetcher
|
|
842
901
|
});
|
|
843
902
|
//#endregion
|
|
844
|
-
export { BACKUP_CACHE_FILE_NAME, Config, LOG_FILE_NAME, __test, apply, inject, name };
|
|
903
|
+
export { BACKUP_CACHE_FILE_NAME, Config, LOG_FILE_NAME, PLUGIN_CACHE_DIR_NAME, __test, apply, inject, name };
|