@lyogvce/dsh-codex-subscription 0.2.12 → 0.2.13
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/index.d.ts +2 -0
- package/lib/index.js +123 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
1. **与本机 Codex CLI / Desktop 100% 共享登录态**
|
|
12
12
|
- 直接读取 `$CODEX_HOME/auth.json`(默认 `~/.codex/auth.json`)。
|
|
13
13
|
- 每次请求前检查 `access_token` 的 JWT `exp`:距离过期不足 5 分钟时自动通过单飞锁(Single-flight)向 `https://auth.openai.com/oauth/token` 刷新,并通过 `realpathSync` + 临时文件 `rename` 原子写回 `auth.json`,保证与 Codex CLI 永不互踢下线。
|
|
14
|
-
2.
|
|
15
|
-
-
|
|
14
|
+
2. **零网络轮询的本地模型目录实时监听、防降级保护与可观测性日志**
|
|
15
|
+
- 直接读取并监听 `~/.codex/models_cache.json`(同时结合 `realpathSync` 目录级防抖 `fs.watch` 实时监听与微秒级 `stat` 校验,兼容软链接目录与临时文件 `rename` 原子替换),提取 `visibility === "list"` 的模型及其 `context_window`、`supported_reasoning_levels`(自动将 `ultra` 映射为 DSH 的 `max` 档位)与 `default_verbosity`;当本地模型缓存新增或变更模型时,自动调用 `ctx.llm.emitAdaptersUpdated()` 通知 DSH 桌面端前端立即刷新模型下拉框,无需重启或刷新页面。
|
|
16
16
|
- **防旧版客户端降级污染**:自动将高版本 `models_cache.json` 备份至 `~/.codex/cache/dsh-codex-subscription/models-cache-backup.json`;当外部旧版进程(如 `dsh-subagent-codex` 内置的 `0.153.4`)将 `models_cache.json` 降级覆写为不含新模型的旧缓存时,自动拦截降级并保留高版本模型目录。
|
|
17
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`)与原生重试策略(`retryPolicy`)**
|
package/lib/index.d.ts
CHANGED
|
@@ -171,6 +171,8 @@ declare function createUsageFetcher(options: UsageFetcherOptions): {
|
|
|
171
171
|
export declare const name = "codex-subscription";
|
|
172
172
|
export declare const inject: string[];
|
|
173
173
|
export declare const LOG_FILE_NAME = "plugin.log";
|
|
174
|
+
export declare const MODELS_CACHE_FILE_NAME = "models_cache.json";
|
|
175
|
+
export declare const MODELS_CACHE_WATCH_DEBOUNCE_MS = 100;
|
|
174
176
|
export declare const Config: any;
|
|
175
177
|
export declare function apply(ctx: PluginContextLike, config?: Record<string, unknown>): void;
|
|
176
178
|
export declare const __test: Readonly<{
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendFileSync, chmodSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { appendFileSync, chmodSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
4
|
import { LlmError, RetryPolicySchema, resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
@@ -704,6 +704,8 @@ function createAuthManager(options) {
|
|
|
704
704
|
const name = "codex-subscription";
|
|
705
705
|
const inject = ["llm"];
|
|
706
706
|
const LOG_FILE_NAME = "plugin.log";
|
|
707
|
+
const MODELS_CACHE_FILE_NAME = "models_cache.json";
|
|
708
|
+
const MODELS_CACHE_WATCH_DEBOUNCE_MS = 100;
|
|
707
709
|
const STREAM_IDLE_TIMEOUT_MS = 3e5;
|
|
708
710
|
const MAX_REQUEST_IMAGE_BYTES = 20971520;
|
|
709
711
|
const REQUEST_IMAGE_PIXEL_BUDGET = 4194304;
|
|
@@ -711,6 +713,27 @@ const REQUEST_IMAGE_MAX_BYTES = 1048576;
|
|
|
711
713
|
const MAX_LOG_FILE_BYTES = 524288;
|
|
712
714
|
const MAX_PERSISTED_SESSIONS = 200;
|
|
713
715
|
const MAX_PERSISTED_PARENTS = 1e3;
|
|
716
|
+
function resolveModelsCacheWatchTargets(codexHome) {
|
|
717
|
+
const dirs = new Set();
|
|
718
|
+
const fileNames = new Set([MODELS_CACHE_FILE_NAME]);
|
|
719
|
+
try {
|
|
720
|
+
dirs.add(realpathSync(codexHome));
|
|
721
|
+
} catch {
|
|
722
|
+
try {
|
|
723
|
+
if (statSync(codexHome).isDirectory()) dirs.add(codexHome);
|
|
724
|
+
} catch {}
|
|
725
|
+
}
|
|
726
|
+
const cachePath = join(codexHome, MODELS_CACHE_FILE_NAME);
|
|
727
|
+
try {
|
|
728
|
+
const realCachePath = realpathSync(cachePath);
|
|
729
|
+
dirs.add(dirname(realCachePath));
|
|
730
|
+
fileNames.add(basename(realCachePath));
|
|
731
|
+
} catch {}
|
|
732
|
+
return {
|
|
733
|
+
dirs: [...dirs],
|
|
734
|
+
fileNames
|
|
735
|
+
};
|
|
736
|
+
}
|
|
714
737
|
function resolvePluginCacheDir(codexHome) {
|
|
715
738
|
return join(codexHome, PLUGIN_CACHE_DIR_NAME);
|
|
716
739
|
}
|
|
@@ -840,7 +863,8 @@ var CodexPiAiAdapter = class extends PiAiAdapter {
|
|
|
840
863
|
function apply(ctx, config = {}) {
|
|
841
864
|
const baseProvider = openaiCodexProvider();
|
|
842
865
|
const baseModels = baseProvider.getModels();
|
|
843
|
-
const
|
|
866
|
+
const boundCodexHome = resolveCodexHome();
|
|
867
|
+
const getCodexHome = () => boundCodexHome;
|
|
844
868
|
const getConfigFastMode = () => readVolatile(config.fastMode, false) === true;
|
|
845
869
|
const getDisabledModels = () => readVolatile(config.disabledModels, DEFAULT_DISABLED_MODELS);
|
|
846
870
|
const getContextWindows = () => readVolatile(config.contextWindows, {});
|
|
@@ -993,7 +1017,77 @@ function apply(ctx, config = {}) {
|
|
|
993
1017
|
let rawRevision = 0;
|
|
994
1018
|
let activeRetryPolicy = resolveConfiguredRetryPolicy();
|
|
995
1019
|
let currentState;
|
|
1020
|
+
let adapterRegistered = false;
|
|
996
1021
|
const catalogByProfiles = new WeakMap();
|
|
1022
|
+
const activeWatchers = new Map();
|
|
1023
|
+
let watchedFileNames = new Set([MODELS_CACHE_FILE_NAME]);
|
|
1024
|
+
let watchDebounceTimer;
|
|
1025
|
+
let watcherDisposed = false;
|
|
1026
|
+
const closeModelsCacheWatchers = () => {
|
|
1027
|
+
if (watchDebounceTimer !== void 0) {
|
|
1028
|
+
clearTimeout(watchDebounceTimer);
|
|
1029
|
+
watchDebounceTimer = void 0;
|
|
1030
|
+
}
|
|
1031
|
+
for (const watcher of activeWatchers.values()) try {
|
|
1032
|
+
watcher.close();
|
|
1033
|
+
} catch {}
|
|
1034
|
+
activeWatchers.clear();
|
|
1035
|
+
};
|
|
1036
|
+
const scheduleModelsCacheCheck = () => {
|
|
1037
|
+
if (watcherDisposed) return;
|
|
1038
|
+
if (watchDebounceTimer !== void 0) clearTimeout(watchDebounceTimer);
|
|
1039
|
+
watchDebounceTimer = setTimeout(() => {
|
|
1040
|
+
watchDebounceTimer = void 0;
|
|
1041
|
+
if (watcherDisposed) return;
|
|
1042
|
+
try {
|
|
1043
|
+
if (!statSync(getCodexHome()).isDirectory()) {
|
|
1044
|
+
watcherDisposed = true;
|
|
1045
|
+
closeModelsCacheWatchers();
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
} catch {
|
|
1049
|
+
watcherDisposed = true;
|
|
1050
|
+
closeModelsCacheWatchers();
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
syncModelsCacheWatchers();
|
|
1054
|
+
ensureSnapshot();
|
|
1055
|
+
}, 100);
|
|
1056
|
+
watchDebounceTimer.unref?.();
|
|
1057
|
+
};
|
|
1058
|
+
const syncModelsCacheWatchers = () => {
|
|
1059
|
+
if (watcherDisposed) return;
|
|
1060
|
+
const { dirs, fileNames } = resolveModelsCacheWatchTargets(getCodexHome());
|
|
1061
|
+
watchedFileNames = fileNames;
|
|
1062
|
+
const nextDirSet = new Set(dirs);
|
|
1063
|
+
for (const [dir, watcher] of activeWatchers.entries()) if (!nextDirSet.has(dir)) {
|
|
1064
|
+
try {
|
|
1065
|
+
watcher.close();
|
|
1066
|
+
} catch {}
|
|
1067
|
+
activeWatchers.delete(dir);
|
|
1068
|
+
}
|
|
1069
|
+
for (const dir of nextDirSet) {
|
|
1070
|
+
if (activeWatchers.has(dir)) continue;
|
|
1071
|
+
try {
|
|
1072
|
+
const watcher = watch(dir, { persistent: false }, (_eventType, rawFilename) => {
|
|
1073
|
+
if (watcherDisposed) return;
|
|
1074
|
+
if (rawFilename != null) {
|
|
1075
|
+
const name = typeof rawFilename === "string" ? rawFilename : String(rawFilename);
|
|
1076
|
+
if (name !== "" && !watchedFileNames.has(name)) return;
|
|
1077
|
+
}
|
|
1078
|
+
scheduleModelsCacheCheck();
|
|
1079
|
+
});
|
|
1080
|
+
watcher.on("error", () => {
|
|
1081
|
+
try {
|
|
1082
|
+
watcher.close();
|
|
1083
|
+
} catch {}
|
|
1084
|
+
activeWatchers.delete(dir);
|
|
1085
|
+
});
|
|
1086
|
+
watcher.unref?.();
|
|
1087
|
+
activeWatchers.set(dir, watcher);
|
|
1088
|
+
} catch {}
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
997
1091
|
const requestTokenAuth = Object.freeze({
|
|
998
1092
|
name: "Codex CLI auth.json token",
|
|
999
1093
|
async resolve({ credential }) {
|
|
@@ -1050,10 +1144,11 @@ function apply(ctx, config = {}) {
|
|
|
1050
1144
|
}
|
|
1051
1145
|
});
|
|
1052
1146
|
}
|
|
1053
|
-
function ensureSnapshot() {
|
|
1147
|
+
function ensureSnapshot(options) {
|
|
1148
|
+
const prevCatalogSig = currentState?.catalogSig;
|
|
1054
1149
|
const retryPolicy = activeRetryPolicy;
|
|
1055
1150
|
const codexHome = getCodexHome();
|
|
1056
|
-
const cachePath = join(codexHome,
|
|
1151
|
+
const cachePath = join(codexHome, MODELS_CACHE_FILE_NAME);
|
|
1057
1152
|
const disabled = getDisabledModels();
|
|
1058
1153
|
const contextWindows = getContextWindows();
|
|
1059
1154
|
const setActiveRaw = (next) => {
|
|
@@ -1172,6 +1267,11 @@ function apply(ctx, config = {}) {
|
|
|
1172
1267
|
retryPolicySig
|
|
1173
1268
|
});
|
|
1174
1269
|
}
|
|
1270
|
+
if (adapterRegistered && !watcherDisposed) syncModelsCacheWatchers();
|
|
1271
|
+
if (adapterRegistered && options?.notifyAdaptersUpdated !== false && prevCatalogSig !== void 0 && nextCatalogSig !== prevCatalogSig) {
|
|
1272
|
+
committedState = currentState;
|
|
1273
|
+
ctx.llm.emitAdaptersUpdated?.();
|
|
1274
|
+
}
|
|
1175
1275
|
return currentState;
|
|
1176
1276
|
}
|
|
1177
1277
|
const adapter = new CodexPiAiAdapter({
|
|
@@ -1202,8 +1302,24 @@ function apply(ctx, config = {}) {
|
|
|
1202
1302
|
const snapProfiles = snapshot?.profiles;
|
|
1203
1303
|
return ((snapProfiles !== void 0 ? catalogByProfiles.get(snapProfiles) : void 0) ?? ensureSnapshot().catalog).metadataById.get(modelId);
|
|
1204
1304
|
});
|
|
1205
|
-
let committedState = ensureSnapshot();
|
|
1305
|
+
let committedState = ensureSnapshot({ notifyAdaptersUpdated: false });
|
|
1206
1306
|
const registration = ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
1307
|
+
adapterRegistered = true;
|
|
1308
|
+
if (typeof ctx.effect === "function") ctx.effect(() => {
|
|
1309
|
+
watcherDisposed = false;
|
|
1310
|
+
syncModelsCacheWatchers();
|
|
1311
|
+
return () => {
|
|
1312
|
+
watcherDisposed = true;
|
|
1313
|
+
closeModelsCacheWatchers();
|
|
1314
|
+
};
|
|
1315
|
+
}, "codex-subscription: models_cache watcher");
|
|
1316
|
+
else {
|
|
1317
|
+
syncModelsCacheWatchers();
|
|
1318
|
+
ctx.on?.("dispose", () => {
|
|
1319
|
+
watcherDisposed = true;
|
|
1320
|
+
closeModelsCacheWatchers();
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1207
1323
|
const mountUsageRoute = (connCtx) => {
|
|
1208
1324
|
const connection = connCtx.get?.("connection");
|
|
1209
1325
|
const registerFetch = connection?.fetch?.register;
|
|
@@ -1284,7 +1400,7 @@ function apply(ctx, config = {}) {
|
|
|
1284
1400
|
activeRetryPolicy = candidateRetryPolicy;
|
|
1285
1401
|
let nextState;
|
|
1286
1402
|
try {
|
|
1287
|
-
nextState = ensureSnapshot();
|
|
1403
|
+
nextState = ensureSnapshot({ notifyAdaptersUpdated: false });
|
|
1288
1404
|
if (nextState.retryPolicySig !== prevCommitted.retryPolicySig) {
|
|
1289
1405
|
registration.replace([PROVIDER]);
|
|
1290
1406
|
committedState = nextState;
|
|
@@ -1326,4 +1442,4 @@ const __test = Object.freeze({
|
|
|
1326
1442
|
createUsageFetcher
|
|
1327
1443
|
});
|
|
1328
1444
|
//#endregion
|
|
1329
|
-
export { BACKUP_CACHE_FILE_NAME, Config, LOG_FILE_NAME, PLUGIN_CACHE_DIR_NAME, RUNTIME_STATE_FILE_NAME, __test, apply, inject, name };
|
|
1445
|
+
export { BACKUP_CACHE_FILE_NAME, Config, LOG_FILE_NAME, MODELS_CACHE_FILE_NAME, MODELS_CACHE_WATCH_DEBOUNCE_MS, PLUGIN_CACHE_DIR_NAME, RUNTIME_STATE_FILE_NAME, __test, apply, inject, name };
|