@lyogvce/dsh-codex-subscription 0.2.10 → 0.2.11
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 +11 -4
- package/lib/index.d.ts +7 -4
- package/lib/index.js +89 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
- 直接读取 `~/.codex/models_cache.json`(每次按文件 `mtime` 与大小做微秒级 `stat` 校验,仅在文件变化时重读解析),提取 `visibility === "list"` 的模型及其 `context_window`、`supported_reasoning_levels`(自动将 `ultra` 映射为 DSH 的 `max` 档位)与 `default_verbosity`。
|
|
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
|
-
3. **支持按配置屏蔽模型(`disabledModels
|
|
18
|
+
3. **支持按配置屏蔽模型(`disabledModels`)、自定义上下文窗口(`contextWindows`)与原生重试策略(`retryPolicy`)**
|
|
19
19
|
- 在插件配置中通过 `disabledModels` 屏蔽指定模型 `slug`,保持模型选择器干净清爽。
|
|
20
20
|
- 通过 `contextWindows` 按模型 `slug` 自定义向 DSH 会话与自动压缩器暴露的预算窗口(例如设为 `400000`);未配置时默认使用模型物理上限 `max_context_window`(`872000`)。同时底层 `PiCodexModel.contextWindow` 始终保持物理上限 `872000`,彻底防止 `dsh-llm-pi-ai` 在预算窗口与物理上限之间误判 `CONTEXT_WINDOW_EXCEEDED`。
|
|
21
|
+
- 通过 `retryPolicy` 暴露 DSH 原生 `RetryPolicySchema`(支持 `normal` / `always` 模式、`maxRetries`、`retryableCodes` 与 `backoff`,并随 `registration.replace` 热更新生效)。未配置时保持宿主默认策略(`mode: "normal"`、`maxRetries: 5`、默认 5 类错误码 `EMPTY_RESPONSE / RATE_LIMIT / SERVER / TIMEOUT / TRANSPORT` 及 `500ms..10000ms` 退避)。**注意:`retryableCodes` 为完整覆盖集合而非追加列表**,显式配置时需同时列出默认错误码与新增错误码。
|
|
21
22
|
4. **原生多轮思考链回放与工具调用**
|
|
22
23
|
- 底层复用 DSH 内置的 `PiAiAdapter` 与 `@earendil-works/pi-ai` 的 `openaiCodexProvider()`,固定使用 `sse` 传输(含 `zstd` 请求体压缩、规范化纯 UUID `session-id` / `x-client-request-id` 缓存头、`reasoning.encrypted_content` 签名回放与图片自动缩放卸载)。
|
|
23
24
|
5. **Codex 订阅限额内联展示与 ⚡ Fast 极速模式一键切换(`conversation.input.right`)**
|
|
@@ -36,9 +37,11 @@
|
|
|
36
37
|
dsh plugin --profile desktop add @lyogvce/dsh-codex-subscription
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
### 2. 配置屏蔽模型(`disabledModels
|
|
40
|
+
### 2. 配置屏蔽模型(`disabledModels`)、上下文窗口(`contextWindows`)与重试策略(`retryPolicy`)
|
|
40
41
|
|
|
41
|
-
默认配置(见 [cordis.patch.yml](cordis.patch.yml))已屏蔽旧模型与备用模型,仅保留主力模型(如 `gpt-6.1-sol`、`gpt-6-astra`)。未配置 `contextWindows` 时,模型默认使用 `872000`
|
|
42
|
+
默认配置(见 [cordis.patch.yml](cordis.patch.yml))已屏蔽旧模型与备用模型,仅保留主力模型(如 `gpt-6.1-sol`、`gpt-6-astra`)。未配置 `contextWindows` 时,模型默认使用 `872000` 上下文窗口;未配置 `retryPolicy` 时默认使用宿主 `normal` 重试策略(最多额外重试 5 次,覆盖 `EMPTY_RESPONSE`、`RATE_LIMIT`、`SERVER`、`TIMEOUT`、`TRANSPORT`)。
|
|
43
|
+
|
|
44
|
+
若希望自定义上下文预算窗口或扩展可重试错误码白名单(**注意:`retryableCodes` 是完整集合而非追加列表,显式填写时需同时保留默认 5 类错误码**),可在 Profile 的 `cordis.patch.yml` 中配置:
|
|
42
45
|
|
|
43
46
|
```yaml
|
|
44
47
|
- id: codex-subscription
|
|
@@ -53,9 +56,13 @@ dsh plugin --profile desktop add @lyogvce/dsh-codex-subscription
|
|
|
53
56
|
contextWindows:
|
|
54
57
|
gpt-6.1-sol: 400000
|
|
55
58
|
gpt-6-astra: 400000
|
|
59
|
+
retryPolicy:
|
|
60
|
+
mode: normal
|
|
61
|
+
maxRetries: 5
|
|
62
|
+
retryableCodes: [ EMPTY_RESPONSE, RATE_LIMIT, SERVER, TIMEOUT, TRANSPORT, QUOTA, ACCOUNT_QUOTA, AUTH, INVALID_REQUEST, PI_AI_ERROR, STREAM_CLOSED, MALFORMED_RESPONSE ]
|
|
56
63
|
```
|
|
57
64
|
|
|
58
|
-
可在 DSH 的 **设置 → 插件 → Codex 订阅直连** 页面或 `cordis.patch.yml` 中随时修改 `disabledModels` 与 `
|
|
65
|
+
可在 DSH 的 **设置 → 插件 → Codex 订阅直连** 页面或 `cordis.patch.yml` 中随时修改 `disabledModels`、`contextWindows` 与 `retryPolicy`,实时热更新生效无需重启。
|
|
59
66
|
|
|
60
67
|
---
|
|
61
68
|
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { PiCodexModel } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
2
2
|
//#region src/host-types.d.ts
|
|
3
|
+
interface AdapterRegistrationHandle {
|
|
4
|
+
(): void;
|
|
5
|
+
replace: (next: string[]) => void;
|
|
6
|
+
}
|
|
3
7
|
interface LlmServiceLike {
|
|
4
|
-
registerAdapter: (providers: string[], adapter: unknown) =>
|
|
5
|
-
replace?: (next: string[]) => void;
|
|
6
|
-
};
|
|
8
|
+
registerAdapter: (providers: string[], adapter: unknown) => AdapterRegistrationHandle;
|
|
7
9
|
emitAdaptersUpdated?: () => void;
|
|
8
10
|
}
|
|
9
11
|
type PluginLogLevel = 'INFO' | 'WARN' | 'ERROR';
|
|
@@ -15,9 +17,10 @@ interface LoggerLike {
|
|
|
15
17
|
}
|
|
16
18
|
interface PluginContextLike {
|
|
17
19
|
llm: LlmServiceLike;
|
|
20
|
+
fiber?: unknown;
|
|
18
21
|
logger?: LoggerLike | undefined;
|
|
19
22
|
get?: (name: string) => unknown;
|
|
20
|
-
on?: (event: string, listener: (...args: unknown[]) =>
|
|
23
|
+
on?: (event: string, listener: (...args: unknown[]) => unknown) => () => void;
|
|
21
24
|
effect?: (factory: () => void | (() => void), label?: string) => () => void;
|
|
22
25
|
inject?: (services: readonly string[], callback: (ctx: PluginContextLike) => void) => () => void;
|
|
23
26
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { appendFileSync, chmodSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
|
-
import { LlmError, resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
4
|
+
import { LlmError, RetryPolicySchema, resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
5
5
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
6
6
|
import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
@@ -705,7 +705,8 @@ function writePersistedRuntimeState(codexHome, ctx, sessionFastModes, sessionPar
|
|
|
705
705
|
const Config = z.object({
|
|
706
706
|
fastMode: z.boolean().default(false).description("新会话默认是否开启 Fast 极速模式(未填时默认 false;单会话可在右下角限额弹窗内独立切换)").volatile(),
|
|
707
707
|
disabledModels: z.array(z.string()).default([...DEFAULT_DISABLED_MODELS]).description("在模型选择器中屏蔽的模型 slug 列表").volatile(),
|
|
708
|
-
contextWindows: z.dict(z.number()).default({}).description("按模型 slug 覆盖上下文窗口大小(tokens),未配置时默认使用 872000").volatile()
|
|
708
|
+
contextWindows: z.dict(z.number()).default({}).description("按模型 slug 覆盖上下文窗口大小(tokens),未配置时默认使用 872000").volatile(),
|
|
709
|
+
retryPolicy: RetryPolicySchema.description("原生 Agent 步骤重试策略(retryableCodes 为完整覆盖集合,显式配置时需同时包含默认错误码与新增错误码)").volatile()
|
|
709
710
|
});
|
|
710
711
|
function readVolatile(field, fallback) {
|
|
711
712
|
const value = typeof field?.get === "function" ? field.get() : field;
|
|
@@ -719,7 +720,7 @@ var CodexPiAiAdapter = class extends PiAiAdapter {
|
|
|
719
720
|
}
|
|
720
721
|
modelInfo(snapshot, provider, model) {
|
|
721
722
|
const info = super.modelInfo(snapshot, provider, model);
|
|
722
|
-
const meta = this.#getModelMetadata(model);
|
|
723
|
+
const meta = this.#getModelMetadata(snapshot, model);
|
|
723
724
|
if (meta === void 0) return info;
|
|
724
725
|
const defaultLevel = meta.defaultReasoningLevel;
|
|
725
726
|
const hasDefaultLevel = defaultLevel !== void 0 && info.reasoning !== void 0 && info.reasoning.efforts.some((e) => e.id === defaultLevel);
|
|
@@ -739,11 +740,12 @@ var CodexPiAiAdapter = class extends PiAiAdapter {
|
|
|
739
740
|
function apply(ctx, config = {}) {
|
|
740
741
|
const baseProvider = openaiCodexProvider();
|
|
741
742
|
const baseModels = baseProvider.getModels();
|
|
742
|
-
const retryPolicy = resolveRetryPolicy(void 0, "codex-subscription retryPolicy");
|
|
743
743
|
const getCodexHome = () => resolveCodexHome();
|
|
744
744
|
const getConfigFastMode = () => readVolatile(config.fastMode, false) === true;
|
|
745
745
|
const getDisabledModels = () => readVolatile(config.disabledModels, DEFAULT_DISABLED_MODELS);
|
|
746
746
|
const getContextWindows = () => readVolatile(config.contextWindows, {});
|
|
747
|
+
const getRawRetryPolicy = () => readVolatile(config.retryPolicy, void 0);
|
|
748
|
+
const resolveConfiguredRetryPolicy = () => resolveRetryPolicy(getRawRetryPolicy(), "codex-subscription retryPolicy");
|
|
747
749
|
const { sessionFastModes, sessionParents } = readPersistedRuntimeState(getCodexHome());
|
|
748
750
|
const recordSessionParent = (childKey, parentKey) => {
|
|
749
751
|
if (childKey === "" || parentKey === "" || childKey === parentKey) return false;
|
|
@@ -889,13 +891,9 @@ function apply(ctx, config = {}) {
|
|
|
889
891
|
let cachedRaw;
|
|
890
892
|
let lastRejectedRaw;
|
|
891
893
|
let rawRevision = 0;
|
|
892
|
-
let
|
|
893
|
-
let
|
|
894
|
-
|
|
895
|
-
models: [],
|
|
896
|
-
metadataById: new Map()
|
|
897
|
-
};
|
|
898
|
-
let currentProfilesMap;
|
|
894
|
+
let activeRetryPolicy = resolveConfiguredRetryPolicy();
|
|
895
|
+
let currentState;
|
|
896
|
+
const catalogByProfiles = new WeakMap();
|
|
899
897
|
const requestTokenAuth = Object.freeze({
|
|
900
898
|
name: "Codex CLI auth.json token",
|
|
901
899
|
async resolve({ credential }) {
|
|
@@ -907,8 +905,7 @@ function apply(ctx, config = {}) {
|
|
|
907
905
|
};
|
|
908
906
|
}
|
|
909
907
|
});
|
|
910
|
-
function wrapOptions(model, options = {}) {
|
|
911
|
-
const { catalog } = ensureSnapshot();
|
|
908
|
+
function wrapOptions(catalog, model, options = {}) {
|
|
912
909
|
const modelMeta = catalog.metadataById.get(model?.id);
|
|
913
910
|
const { temperature: _temperature, sessionId, onPayload, ...rest } = options;
|
|
914
911
|
const initiatorId = (ctx.get?.("agents"))?.currentInitiator?.()?.id;
|
|
@@ -928,25 +925,28 @@ function apply(ctx, config = {}) {
|
|
|
928
925
|
resolveCredentials: () => authManager.resolveCredentials(),
|
|
929
926
|
buildHeaders: (authHeaders) => buildCodexHeaders(authHeaders, ensureSnapshot().catalog.clientVersion)
|
|
930
927
|
});
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
928
|
+
function createSnapshotPiProvider(snapshotCatalog) {
|
|
929
|
+
return Object.freeze({
|
|
930
|
+
...baseProvider,
|
|
931
|
+
id: PROVIDER,
|
|
932
|
+
name: DISPLAY_NAME,
|
|
933
|
+
auth: Object.freeze({
|
|
934
|
+
...baseProvider.auth,
|
|
935
|
+
apiKey: requestTokenAuth
|
|
936
|
+
}),
|
|
937
|
+
getModels: () => snapshotCatalog.models,
|
|
938
|
+
stream: (model, context, options) => {
|
|
939
|
+
usageFetcher.invalidate();
|
|
940
|
+
return baseProvider.stream(model, context, wrapOptions(snapshotCatalog, model, options));
|
|
941
|
+
},
|
|
942
|
+
streamSimple: (model, context, options) => {
|
|
943
|
+
usageFetcher.invalidate();
|
|
944
|
+
return baseProvider.streamSimple(model, context, wrapOptions(snapshotCatalog, model, options));
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
}
|
|
949
948
|
function ensureSnapshot() {
|
|
949
|
+
const retryPolicy = activeRetryPolicy;
|
|
950
950
|
const codexHome = getCodexHome();
|
|
951
951
|
const cachePath = join(codexHome, "models_cache.json");
|
|
952
952
|
const disabled = getDisabledModels();
|
|
@@ -1019,24 +1019,30 @@ function apply(ctx, config = {}) {
|
|
|
1019
1019
|
}
|
|
1020
1020
|
}
|
|
1021
1021
|
}
|
|
1022
|
-
const
|
|
1022
|
+
const nextCatalogSig = JSON.stringify([
|
|
1023
1023
|
rawRevision,
|
|
1024
1024
|
disabled,
|
|
1025
1025
|
contextWindows
|
|
1026
1026
|
]);
|
|
1027
|
-
|
|
1028
|
-
|
|
1027
|
+
let nextCatalog = currentState?.catalog;
|
|
1028
|
+
if (nextCatalog === void 0 || nextCatalogSig !== currentState?.catalogSig) {
|
|
1029
|
+
nextCatalog = cachedRaw !== void 0 ? parseModelsCache(cachedRaw, baseModels, disabled, contextWindows) : {
|
|
1029
1030
|
clientVersion: DEFAULT_CLIENT_VERSION,
|
|
1030
1031
|
models: [],
|
|
1031
1032
|
metadataById: new Map()
|
|
1032
1033
|
};
|
|
1033
|
-
appendPluginLog(codexHome, ctx,
|
|
1034
|
-
clientVersion:
|
|
1035
|
-
models:
|
|
1034
|
+
appendPluginLog(codexHome, ctx, nextCatalog.models.length === 0 ? "WARN" : "INFO", "catalog.snapshot", {
|
|
1035
|
+
clientVersion: nextCatalog.clientVersion,
|
|
1036
|
+
models: nextCatalog.models.map((m) => m.id),
|
|
1036
1037
|
disabledModels: disabled,
|
|
1037
1038
|
contextWindows
|
|
1038
1039
|
});
|
|
1039
|
-
|
|
1040
|
+
}
|
|
1041
|
+
const retryPolicySig = JSON.stringify(retryPolicy);
|
|
1042
|
+
const nextSnapshotSig = JSON.stringify([nextCatalogSig, retryPolicySig]);
|
|
1043
|
+
if (currentState === void 0 || nextSnapshotSig !== currentState.snapshotSig) {
|
|
1044
|
+
const piProvider = createSnapshotPiProvider(nextCatalog);
|
|
1045
|
+
const profileEntry = Object.freeze({
|
|
1040
1046
|
provider: PROVIDER,
|
|
1041
1047
|
displayName: DISPLAY_NAME,
|
|
1042
1048
|
piProvider,
|
|
@@ -1049,13 +1055,19 @@ function apply(ctx, config = {}) {
|
|
|
1049
1055
|
streamIdleTimeoutMs: STREAM_IDLE_TIMEOUT_MS,
|
|
1050
1056
|
cacheRetention: "short",
|
|
1051
1057
|
transport: "sse"
|
|
1052
|
-
})
|
|
1053
|
-
|
|
1058
|
+
});
|
|
1059
|
+
const profiles = new Map([[PROVIDER, profileEntry]]);
|
|
1060
|
+
catalogByProfiles.set(profiles, nextCatalog);
|
|
1061
|
+
currentState = Object.freeze({
|
|
1062
|
+
catalog: nextCatalog,
|
|
1063
|
+
catalogSig: nextCatalogSig,
|
|
1064
|
+
profiles,
|
|
1065
|
+
snapshotSig: nextSnapshotSig,
|
|
1066
|
+
retryPolicy,
|
|
1067
|
+
retryPolicySig
|
|
1068
|
+
});
|
|
1054
1069
|
}
|
|
1055
|
-
return
|
|
1056
|
-
catalog: currentCatalog,
|
|
1057
|
-
profiles: currentProfilesMap
|
|
1058
|
-
};
|
|
1070
|
+
return currentState;
|
|
1059
1071
|
}
|
|
1060
1072
|
const adapter = new CodexPiAiAdapter({
|
|
1061
1073
|
profiles: () => ensureSnapshot().profiles,
|
|
@@ -1081,8 +1093,12 @@ function apply(ctx, config = {}) {
|
|
|
1081
1093
|
onReplayDegrade: (provider, model, reason) => {
|
|
1082
1094
|
console.error(`[dsh-codex-subscription] replay state for "${provider}/${model}" degraded (${reason})`);
|
|
1083
1095
|
}
|
|
1084
|
-
}, (modelId) =>
|
|
1085
|
-
|
|
1096
|
+
}, (snapshot, modelId) => {
|
|
1097
|
+
const snapProfiles = snapshot?.profiles;
|
|
1098
|
+
return ((snapProfiles !== void 0 ? catalogByProfiles.get(snapProfiles) : void 0) ?? ensureSnapshot().catalog).metadataById.get(modelId);
|
|
1099
|
+
});
|
|
1100
|
+
let committedState = ensureSnapshot();
|
|
1101
|
+
const registration = ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
1086
1102
|
const mountUsageRoute = (connCtx) => {
|
|
1087
1103
|
const connection = connCtx.get?.("connection");
|
|
1088
1104
|
const registerFetch = connection?.fetch?.register;
|
|
@@ -1147,8 +1163,34 @@ function apply(ctx, config = {}) {
|
|
|
1147
1163
|
if (inspectSessionParentFromHost(agent?.session, agent)) writePersistedRuntimeState(getCodexHome(), ctx, sessionFastModes, sessionParents);
|
|
1148
1164
|
}
|
|
1149
1165
|
});
|
|
1166
|
+
ctx.on?.("internal/config", function(rawInput, next) {
|
|
1167
|
+
const raw = typeof next === "function" ? next() : rawInput;
|
|
1168
|
+
if (ctx.fiber !== void 0 && this !== ctx.fiber) return raw;
|
|
1169
|
+
if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
1170
|
+
const candidateRetry = readVolatile(raw.retryPolicy, void 0);
|
|
1171
|
+
resolveRetryPolicy(candidateRetry, "codex-subscription retryPolicy");
|
|
1172
|
+
}
|
|
1173
|
+
return raw;
|
|
1174
|
+
});
|
|
1150
1175
|
ctx.on?.("loader/volatile-update", () => {
|
|
1151
|
-
|
|
1176
|
+
const candidateRetryPolicy = resolveConfiguredRetryPolicy();
|
|
1177
|
+
const prevRetryPolicy = activeRetryPolicy;
|
|
1178
|
+
const prevCommitted = committedState;
|
|
1179
|
+
activeRetryPolicy = candidateRetryPolicy;
|
|
1180
|
+
let nextState;
|
|
1181
|
+
try {
|
|
1182
|
+
nextState = ensureSnapshot();
|
|
1183
|
+
if (nextState.retryPolicySig !== prevCommitted.retryPolicySig) {
|
|
1184
|
+
registration.replace([PROVIDER]);
|
|
1185
|
+
committedState = nextState;
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
activeRetryPolicy = prevRetryPolicy;
|
|
1190
|
+
currentState = prevCommitted;
|
|
1191
|
+
throw error;
|
|
1192
|
+
}
|
|
1193
|
+
committedState = nextState;
|
|
1152
1194
|
ctx.llm.emitAdaptersUpdated?.();
|
|
1153
1195
|
});
|
|
1154
1196
|
}
|