@lyogvce/dsh-codex-subscription 0.2.9 → 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 +12 -5
- package/lib/client.js +136 -33
- package/lib/index.d.ts +9 -4
- package/lib/index.js +274 -68
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,14 +15,15 @@
|
|
|
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`)**
|
|
24
25
|
- 当会话选中 `openai-codex` 模型时,在输入框右下角紧邻模型选择器左侧展示限额胶囊(根据 `wham/usage` 返回的 `limit_window_seconds` 自适应展示 Plus 的 `5h X% · 7d Y%` 双窗口或 Pro 的 `7d X%` 单窗口,内置 60 秒缓存、单飞合并与每轮回复结束自动刷新),点击可展开查看对应窗口进度条、重置倒计时与手动刷新按钮;切换至其他 Provider 模型时自动隐藏。
|
|
25
|
-
- **⚡
|
|
26
|
+
- **⚡ 会话树级 Fast 极速模式(`service_tier: "priority"`,最高 2x 推理速度,主会话与 Subagent / 智能体团队同开同关)**:每个主会话可在限额弹窗底部独立开关 Fast 模式(不同主会话互不影响,新会话默认继承插件设置中的 `fastMode`,未配置时默认 `false` 关闭),该主会话派生的所有 `subagent`、`subagent_fork`、`workflow` 子任务以及智能体团队成员(`spawn_teammate`)均自动绑定至同一主会话开关,实时联动、同开同关;状态自动持久化至 `~/.codex/cache/dsh-codex-subscription/runtime-state.json`。开启后胶囊显示闪电标识 `⚡`,并在请求体中自动注入 `"service_tier": "priority"`。
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
@@ -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/client.js
CHANGED
|
@@ -18,6 +18,26 @@ window.__ModuleLoader__.load({
|
|
|
18
18
|
const stripped = sessionId.trim().replace(/^session-/iu, "").trim();
|
|
19
19
|
return stripped.length > 0 ? stripped : void 0;
|
|
20
20
|
}
|
|
21
|
+
function resolveRootSessionId(sessionId, sessionParents, directParentSessionId) {
|
|
22
|
+
const startKey = normalizeCodexSessionId(sessionId);
|
|
23
|
+
const directParentKey = normalizeCodexSessionId(directParentSessionId);
|
|
24
|
+
if (startKey === void 0) return directParentKey ?? "__default__";
|
|
25
|
+
const readParent = (key) => {
|
|
26
|
+
if (key === startKey && directParentKey !== void 0 && directParentKey !== startKey) return directParentKey;
|
|
27
|
+
if (!sessionParents) return void 0;
|
|
28
|
+
const raw = sessionParents instanceof Map ? sessionParents.get(key) : sessionParents[key];
|
|
29
|
+
return typeof raw === "string" ? normalizeCodexSessionId(raw) : void 0;
|
|
30
|
+
};
|
|
31
|
+
let current = startKey;
|
|
32
|
+
const visited = new Set([current]);
|
|
33
|
+
while (visited.size <= 32) {
|
|
34
|
+
const parent = readParent(current);
|
|
35
|
+
if (parent === void 0 || visited.has(parent)) break;
|
|
36
|
+
visited.add(parent);
|
|
37
|
+
current = parent;
|
|
38
|
+
}
|
|
39
|
+
return current;
|
|
40
|
+
}
|
|
21
41
|
//#endregion
|
|
22
42
|
//#region src/usage.ts
|
|
23
43
|
function safeErrorMessage(error) {
|
|
@@ -30,14 +50,16 @@ window.__ModuleLoader__.load({
|
|
|
30
50
|
//#endregion
|
|
31
51
|
//#region src/client/quota.ts
|
|
32
52
|
const RING_CIRCUMFERENCE = 2 * Math.PI * 5.5;
|
|
33
|
-
const DEFAULT_SESSION_KEY = "__default__";
|
|
34
53
|
const DEFAULT_PRIMARY_WINDOW_SECONDS = 18e3;
|
|
35
54
|
const DEFAULT_SECONDARY_WINDOW_SECONDS = 604800;
|
|
36
|
-
function resolveSessionFastMode(snapshot, sessionId) {
|
|
55
|
+
function resolveSessionFastMode(snapshot, sessionId, parentSessionId) {
|
|
37
56
|
if (!snapshot) return false;
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
if (typeof
|
|
57
|
+
const rootKey = resolveRootSessionId(sessionId, snapshot.sessionParents, parentSessionId);
|
|
58
|
+
const rootVal = snapshot.sessionFastModes?.[rootKey];
|
|
59
|
+
if (typeof rootVal === "boolean") return rootVal;
|
|
60
|
+
const selfKey = normalizeCodexSessionId(sessionId) ?? "__default__";
|
|
61
|
+
const selfVal = snapshot.sessionFastModes?.[selfKey];
|
|
62
|
+
if (typeof selfVal === "boolean") return selfVal;
|
|
41
63
|
return snapshot.defaultFastMode === true;
|
|
42
64
|
}
|
|
43
65
|
function resolveActiveProvider(directorySnapshot, projectedSelection) {
|
|
@@ -154,13 +176,14 @@ window.__ModuleLoader__.load({
|
|
|
154
176
|
quota: null,
|
|
155
177
|
defaultFastMode: false,
|
|
156
178
|
sessionFastModes: {},
|
|
179
|
+
sessionParents: {},
|
|
157
180
|
loading: false,
|
|
158
181
|
error: null
|
|
159
182
|
};
|
|
160
183
|
let confirmedSessionFastModes = {};
|
|
161
|
-
let
|
|
162
|
-
const
|
|
163
|
-
const
|
|
184
|
+
let knownSessionParents = {};
|
|
185
|
+
const latestSeqByNode = new Map();
|
|
186
|
+
const pendingBySeq = new Map();
|
|
164
187
|
let inflight = null;
|
|
165
188
|
let inflightForce = false;
|
|
166
189
|
let requestSeq = 0;
|
|
@@ -171,10 +194,63 @@ window.__ModuleLoader__.load({
|
|
|
171
194
|
state = next;
|
|
172
195
|
for (const listener of listeners) listener();
|
|
173
196
|
}
|
|
197
|
+
function recordClientSessionParent(childKey, parentKey) {
|
|
198
|
+
if (childKey === "" || parentKey === "" || childKey === parentKey) return;
|
|
199
|
+
let cursor = parentKey;
|
|
200
|
+
let depth = 0;
|
|
201
|
+
while (cursor !== void 0 && depth < 32) {
|
|
202
|
+
if (cursor === childKey) return;
|
|
203
|
+
cursor = knownSessionParents[cursor];
|
|
204
|
+
depth += 1;
|
|
205
|
+
}
|
|
206
|
+
if (knownSessionParents[childKey] === parentKey) return;
|
|
207
|
+
knownSessionParents = {
|
|
208
|
+
...knownSessionParents,
|
|
209
|
+
[childKey]: parentKey
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function mergeServerSessionParents(raw) {
|
|
213
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
214
|
+
for (const [child, parent] of Object.entries(raw)) {
|
|
215
|
+
const childKey = normalizeCodexSessionId(child);
|
|
216
|
+
const parentKey = typeof parent === "string" ? normalizeCodexSessionId(parent) : void 0;
|
|
217
|
+
if (childKey !== void 0 && parentKey !== void 0) recordClientSessionParent(childKey, parentKey);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function isTreeSeqSuperseded(seq, selfKey, parentKey) {
|
|
221
|
+
const myRoot = resolveRootSessionId(selfKey, knownSessionParents, parentKey);
|
|
222
|
+
for (const [nodeKey, nodeSeq] of latestSeqByNode.entries()) if (nodeSeq > seq && resolveRootSessionId(nodeKey, knownSessionParents) === myRoot) return true;
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
function collectTreeKeysForRoot(rootKey, extraKeys = []) {
|
|
226
|
+
const keys = new Set([rootKey, ...extraKeys]);
|
|
227
|
+
for (const k of Object.keys(knownSessionParents)) if (resolveRootSessionId(k, knownSessionParents) === rootKey) keys.add(k);
|
|
228
|
+
for (const k of Object.keys(confirmedSessionFastModes)) if (resolveRootSessionId(k, knownSessionParents) === rootKey) keys.add(k);
|
|
229
|
+
for (const k of latestSeqByNode.keys()) if (resolveRootSessionId(k, knownSessionParents) === rootKey) keys.add(k);
|
|
230
|
+
return [...keys];
|
|
231
|
+
}
|
|
174
232
|
function deriveSessionFastModes() {
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
233
|
+
const merged = {};
|
|
234
|
+
for (const [k, v] of Object.entries(confirmedSessionFastModes)) {
|
|
235
|
+
const rootKey = resolveRootSessionId(k, knownSessionParents);
|
|
236
|
+
const rootVal = confirmedSessionFastModes[rootKey] ?? v;
|
|
237
|
+
merged[rootKey] = rootVal;
|
|
238
|
+
merged[k] = rootVal;
|
|
239
|
+
}
|
|
240
|
+
for (const childKey of Object.keys(knownSessionParents)) {
|
|
241
|
+
const rootKey = resolveRootSessionId(childKey, knownSessionParents);
|
|
242
|
+
if (typeof merged[rootKey] === "boolean") merged[childKey] = merged[rootKey];
|
|
243
|
+
}
|
|
244
|
+
for (const pending of pendingBySeq.values()) {
|
|
245
|
+
if (isTreeSeqSuperseded(pending.seq, pending.selfKey, pending.parentKey)) continue;
|
|
246
|
+
const rootKey = resolveRootSessionId(pending.selfKey, knownSessionParents, pending.parentKey);
|
|
247
|
+
const extra = pending.parentKey !== void 0 ? [
|
|
248
|
+
rootKey,
|
|
249
|
+
pending.parentKey,
|
|
250
|
+
pending.selfKey
|
|
251
|
+
] : [rootKey, pending.selfKey];
|
|
252
|
+
for (const k of collectTreeKeysForRoot(rootKey, extra)) merged[k] = pending.enabled;
|
|
253
|
+
}
|
|
178
254
|
return merged;
|
|
179
255
|
}
|
|
180
256
|
function parseServerSessionFastModes(raw) {
|
|
@@ -185,20 +261,22 @@ window.__ModuleLoader__.load({
|
|
|
185
261
|
}
|
|
186
262
|
function mergeFastStateFromPayload(payload, allowSessionReplace) {
|
|
187
263
|
const defaultFastMode = typeof payload?.defaultFastMode === "boolean" ? payload.defaultFastMode : state.defaultFastMode;
|
|
264
|
+
mergeServerSessionParents(payload?.sessionParents);
|
|
188
265
|
if (allowSessionReplace) {
|
|
189
266
|
const parsedMap = parseServerSessionFastModes(payload?.sessionFastModes);
|
|
190
267
|
if (parsedMap !== void 0) confirmedSessionFastModes = parsedMap;
|
|
191
268
|
}
|
|
192
269
|
return {
|
|
193
270
|
defaultFastMode,
|
|
194
|
-
sessionFastModes: deriveSessionFastModes()
|
|
271
|
+
sessionFastModes: deriveSessionFastModes(),
|
|
272
|
+
sessionParents: knownSessionParents
|
|
195
273
|
};
|
|
196
274
|
}
|
|
197
275
|
async function refresh(force = false) {
|
|
198
276
|
if (!force && state.quota !== null && nowFn() - state.quota.fetchedAt < ttlMs) return;
|
|
199
277
|
if (inflight !== null && (!force || inflightForce)) return inflight;
|
|
200
278
|
const seq = ++requestSeq;
|
|
201
|
-
const canReplaceFastAtStart =
|
|
279
|
+
const canReplaceFastAtStart = pendingBySeq.size === 0;
|
|
202
280
|
const fastEpochAtStart = fastMutationEpoch;
|
|
203
281
|
inflightForce = force;
|
|
204
282
|
setState({
|
|
@@ -224,7 +302,7 @@ window.__ModuleLoader__.load({
|
|
|
224
302
|
}
|
|
225
303
|
const payload = await response.json();
|
|
226
304
|
if (seq !== requestSeq) return;
|
|
227
|
-
const fastState = mergeFastStateFromPayload(payload, canReplaceFastAtStart &&
|
|
305
|
+
const fastState = mergeFastStateFromPayload(payload, canReplaceFastAtStart && pendingBySeq.size === 0 && fastEpochAtStart === fastMutationEpoch);
|
|
228
306
|
if (payload && payload.ok === true && payload.quota) setState({
|
|
229
307
|
quota: payload.quota,
|
|
230
308
|
...fastState,
|
|
@@ -258,29 +336,38 @@ window.__ModuleLoader__.load({
|
|
|
258
336
|
inflight = task;
|
|
259
337
|
return task;
|
|
260
338
|
}
|
|
261
|
-
async function setFastMode(sessionId, enabled) {
|
|
339
|
+
async function setFastMode(sessionId, enabled, parentSessionId) {
|
|
262
340
|
const nextFastMode = Boolean(enabled);
|
|
263
|
-
const
|
|
341
|
+
const selfKey = normalizeCodexSessionId(sessionId) ?? "__default__";
|
|
342
|
+
const parentKey = normalizeCodexSessionId(parentSessionId);
|
|
343
|
+
if (parentKey !== void 0 && parentKey !== selfKey) recordClientSessionParent(selfKey, parentKey);
|
|
264
344
|
const seq = ++fastModeSeq;
|
|
265
345
|
fastMutationEpoch += 1;
|
|
266
|
-
|
|
267
|
-
|
|
346
|
+
latestSeqByNode.set(selfKey, seq);
|
|
347
|
+
if (parentKey !== void 0) latestSeqByNode.set(parentKey, seq);
|
|
348
|
+
const initialRoot = resolveRootSessionId(selfKey, knownSessionParents, parentKey);
|
|
349
|
+
latestSeqByNode.set(initialRoot, seq);
|
|
350
|
+
pendingBySeq.set(seq, {
|
|
268
351
|
seq,
|
|
352
|
+
selfKey,
|
|
353
|
+
parentKey,
|
|
269
354
|
enabled: nextFastMode
|
|
270
355
|
});
|
|
271
356
|
const rollback = (errorMsg) => {
|
|
272
|
-
|
|
357
|
+
pendingBySeq.delete(seq);
|
|
358
|
+
if (isTreeSeqSuperseded(seq, selfKey, parentKey)) return;
|
|
273
359
|
fastMutationEpoch += 1;
|
|
274
|
-
pendingBySession.delete(key);
|
|
275
360
|
setState({
|
|
276
361
|
...state,
|
|
277
362
|
sessionFastModes: deriveSessionFastModes(),
|
|
363
|
+
sessionParents: knownSessionParents,
|
|
278
364
|
error: errorMsg
|
|
279
365
|
});
|
|
280
366
|
};
|
|
281
367
|
setState({
|
|
282
368
|
...state,
|
|
283
369
|
sessionFastModes: deriveSessionFastModes(),
|
|
370
|
+
sessionParents: knownSessionParents,
|
|
284
371
|
error: null
|
|
285
372
|
});
|
|
286
373
|
try {
|
|
@@ -291,41 +378,55 @@ window.__ModuleLoader__.load({
|
|
|
291
378
|
Accept: "application/json"
|
|
292
379
|
},
|
|
293
380
|
body: JSON.stringify({
|
|
294
|
-
sessionId:
|
|
381
|
+
sessionId: selfKey,
|
|
295
382
|
fastMode: nextFastMode
|
|
296
383
|
})
|
|
297
384
|
});
|
|
298
|
-
if (latestSeqBySession.get(key) !== seq) return;
|
|
299
385
|
if (!response.ok) {
|
|
300
386
|
const errPayload = await response.json().catch(() => null);
|
|
387
|
+
mergeServerSessionParents(errPayload?.sessionParents);
|
|
301
388
|
rollback(typeof errPayload?.error === "string" && errPayload.error !== "" ? errPayload.error : `HTTP ${response.status}`);
|
|
302
389
|
return;
|
|
303
390
|
}
|
|
304
391
|
const payload = await response.json();
|
|
305
|
-
|
|
392
|
+
mergeServerSessionParents(payload?.sessionParents);
|
|
393
|
+
if (isTreeSeqSuperseded(seq, selfKey, parentKey)) {
|
|
394
|
+
pendingBySeq.delete(seq);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
306
397
|
if (!payload || payload.ok === false) {
|
|
307
398
|
rollback(typeof payload?.error === "string" && payload.error !== "" ? payload.error : "Invalid response");
|
|
308
399
|
return;
|
|
309
400
|
}
|
|
310
401
|
fastMutationEpoch += 1;
|
|
311
|
-
|
|
402
|
+
pendingBySeq.delete(seq);
|
|
403
|
+
const actualRootKey = resolveRootSessionId(selfKey, knownSessionParents, parentKey);
|
|
404
|
+
latestSeqByNode.set(actualRootKey, Math.max(latestSeqByNode.get(actualRootKey) ?? 0, seq));
|
|
312
405
|
const parsedMap = parseServerSessionFastModes(payload.sessionFastModes);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
406
|
+
const nextConfirmed = { ...confirmedSessionFastModes };
|
|
407
|
+
if (parsedMap !== void 0) {
|
|
408
|
+
for (const [k, v] of Object.entries(parsedMap)) if (!isTreeSeqSuperseded(seq, k, void 0)) nextConfirmed[k] = v;
|
|
409
|
+
}
|
|
410
|
+
const effectiveVal = parsedMap?.[selfKey] ?? parsedMap?.[actualRootKey] ?? nextFastMode;
|
|
411
|
+
const extraKeys = parentKey !== void 0 && parentKey !== selfKey ? [
|
|
412
|
+
actualRootKey,
|
|
413
|
+
parentKey,
|
|
414
|
+
selfKey
|
|
415
|
+
] : [actualRootKey, selfKey];
|
|
416
|
+
for (const k of collectTreeKeysForRoot(actualRootKey, extraKeys)) nextConfirmed[k] = effectiveVal;
|
|
417
|
+
confirmedSessionFastModes = nextConfirmed;
|
|
320
418
|
const defaultFastMode = typeof payload.defaultFastMode === "boolean" ? payload.defaultFastMode : state.defaultFastMode;
|
|
321
419
|
setState({
|
|
322
420
|
...state,
|
|
323
421
|
defaultFastMode,
|
|
324
422
|
sessionFastModes: deriveSessionFastModes(),
|
|
423
|
+
sessionParents: knownSessionParents,
|
|
325
424
|
error: null
|
|
326
425
|
});
|
|
327
426
|
} catch (error) {
|
|
328
427
|
rollback(safeErrorMessage(error));
|
|
428
|
+
} finally {
|
|
429
|
+
pendingBySeq.delete(seq);
|
|
329
430
|
}
|
|
330
431
|
}
|
|
331
432
|
return {
|
|
@@ -430,6 +531,8 @@ window.__ModuleLoader__.load({
|
|
|
430
531
|
const directorySnap = React.useSyncExternalStore(modelStore.subscribe, modelStore.getSnapshot);
|
|
431
532
|
const projectedSelection = typeof useProjection === "function" ? useProjection("modelSelection") : void 0;
|
|
432
533
|
const running = typeof useSession === "function" ? Boolean(useSession((s) => s?.running)) : false;
|
|
534
|
+
const rawParentSessionId = typeof useSession === "function" ? useSession((s) => s?.subagent?.address?.parentSessionId) : void 0;
|
|
535
|
+
const parentSessionId = typeof rawParentSessionId === "string" ? rawParentSessionId : void 0;
|
|
433
536
|
const isCodex = isCodexProvider(resolveActiveProvider(directorySnap, projectedSelection));
|
|
434
537
|
const quotaSnap = React.useSyncExternalStore(quotaStore.subscribe, quotaStore.getSnapshot);
|
|
435
538
|
const [open, setOpen] = React.useState(false);
|
|
@@ -489,7 +592,7 @@ window.__ModuleLoader__.load({
|
|
|
489
592
|
})), h("div", { className: "dsh-codex-quota-meta" }, h("span", null, `${t("quota.remainPrefix")}${remainPercent}%`), countdown !== "" ? h("span", null, countdown) : null));
|
|
490
593
|
};
|
|
491
594
|
const quota = quotaSnap.quota;
|
|
492
|
-
const isFastMode = resolveSessionFastMode(quotaSnap, sessionId);
|
|
595
|
+
const isFastMode = resolveSessionFastMode(quotaSnap, sessionId, parentSessionId);
|
|
493
596
|
const secondaryReached = Boolean(quota?.secondary && quota.secondary.usedPercent >= 100);
|
|
494
597
|
const primaryReached = Boolean(quota?.limitReached && !secondaryReached);
|
|
495
598
|
return h("span", {
|
|
@@ -549,7 +652,7 @@ window.__ModuleLoader__.load({
|
|
|
549
652
|
"aria-label": t("quota.fastTitle"),
|
|
550
653
|
"data-checked": isFastMode ? "true" : "false",
|
|
551
654
|
onClick: () => {
|
|
552
|
-
quotaStore.setFastMode(sessionId, !isFastMode);
|
|
655
|
+
quotaStore.setFastMode(sessionId, !isFastMode, parentSessionId);
|
|
553
656
|
}
|
|
554
657
|
}, h("span", { className: "dsh-codex-fast-switch-thumb" }))), quotaSnap.error !== null ? h("div", { className: "dsh-codex-quota-error" }, quotaSnap.error) : null) : null);
|
|
555
658
|
}
|
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: () =>
|
|
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
|
}
|
|
@@ -134,12 +137,14 @@ type CodexQuotaResponse = {
|
|
|
134
137
|
readonly quota: CodexQuotaSnapshot;
|
|
135
138
|
readonly defaultFastMode?: boolean | undefined;
|
|
136
139
|
readonly sessionFastModes?: Readonly<Record<string, boolean>> | undefined;
|
|
140
|
+
readonly sessionParents?: Readonly<Record<string, string>> | undefined;
|
|
137
141
|
} | {
|
|
138
142
|
readonly ok: false;
|
|
139
143
|
readonly error: string;
|
|
140
144
|
readonly quota?: CodexQuotaSnapshot | undefined;
|
|
141
145
|
readonly defaultFastMode?: boolean | undefined;
|
|
142
146
|
readonly sessionFastModes?: Readonly<Record<string, boolean>> | undefined;
|
|
147
|
+
readonly sessionParents?: Readonly<Record<string, string>> | undefined;
|
|
143
148
|
};
|
|
144
149
|
interface UsageFetcherOptions {
|
|
145
150
|
readonly resolveCredentials: () => Promise<CodexResolvedCredentials>;
|
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";
|
|
@@ -16,6 +16,7 @@ const DEFAULT_CONTEXT_WINDOW = 872e3;
|
|
|
16
16
|
const PLUGIN_CACHE_DIR_NAME = "cache/dsh-codex-subscription";
|
|
17
17
|
const BACKUP_CACHE_FILE_NAME = "models-cache-backup.json";
|
|
18
18
|
const RUNTIME_STATE_FILE_NAME = "runtime-state.json";
|
|
19
|
+
const DEFAULT_SESSION_KEY = "__default__";
|
|
19
20
|
const THINKING_LEVELS = [
|
|
20
21
|
"off",
|
|
21
22
|
"minimal",
|
|
@@ -44,6 +45,26 @@ function normalizeCodexSessionId(sessionId) {
|
|
|
44
45
|
const stripped = sessionId.trim().replace(/^session-/iu, "").trim();
|
|
45
46
|
return stripped.length > 0 ? stripped : void 0;
|
|
46
47
|
}
|
|
48
|
+
function resolveRootSessionId(sessionId, sessionParents, directParentSessionId) {
|
|
49
|
+
const startKey = normalizeCodexSessionId(sessionId);
|
|
50
|
+
const directParentKey = normalizeCodexSessionId(directParentSessionId);
|
|
51
|
+
if (startKey === void 0) return directParentKey ?? "__default__";
|
|
52
|
+
const readParent = (key) => {
|
|
53
|
+
if (key === startKey && directParentKey !== void 0 && directParentKey !== startKey) return directParentKey;
|
|
54
|
+
if (!sessionParents) return void 0;
|
|
55
|
+
const raw = sessionParents instanceof Map ? sessionParents.get(key) : sessionParents[key];
|
|
56
|
+
return typeof raw === "string" ? normalizeCodexSessionId(raw) : void 0;
|
|
57
|
+
};
|
|
58
|
+
let current = startKey;
|
|
59
|
+
const visited = new Set([current]);
|
|
60
|
+
while (visited.size <= 32) {
|
|
61
|
+
const parent = readParent(current);
|
|
62
|
+
if (parent === void 0 || visited.has(parent)) break;
|
|
63
|
+
visited.add(parent);
|
|
64
|
+
current = parent;
|
|
65
|
+
}
|
|
66
|
+
return current;
|
|
67
|
+
}
|
|
47
68
|
function isSameModelsCatalogPayload(a, b) {
|
|
48
69
|
if (!a || !b) return false;
|
|
49
70
|
const { fetched_at: _fa, ...restA } = a;
|
|
@@ -589,7 +610,7 @@ const REQUEST_IMAGE_PIXEL_BUDGET = 4194304;
|
|
|
589
610
|
const REQUEST_IMAGE_MAX_BYTES = 1048576;
|
|
590
611
|
const MAX_LOG_FILE_BYTES = 524288;
|
|
591
612
|
const MAX_PERSISTED_SESSIONS = 200;
|
|
592
|
-
const
|
|
613
|
+
const MAX_PERSISTED_PARENTS = 1e3;
|
|
593
614
|
function resolvePluginCacheDir(codexHome) {
|
|
594
615
|
return join(codexHome, PLUGIN_CACHE_DIR_NAME);
|
|
595
616
|
}
|
|
@@ -649,19 +670,34 @@ function writeBackupCache(codexHome, ctx, raw) {
|
|
|
649
670
|
appendPluginLog(codexHome, ctx, "WARN", "catalog.backup_write_error", { error: safeErrorMessage(error) });
|
|
650
671
|
}
|
|
651
672
|
}
|
|
652
|
-
function
|
|
653
|
-
const
|
|
673
|
+
function readPersistedRuntimeState(codexHome) {
|
|
674
|
+
const sessionFastModes = new Map();
|
|
675
|
+
const sessionParents = new Map();
|
|
654
676
|
try {
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
677
|
+
const raw = JSON.parse(readFileSync(join(resolvePluginCacheDir(codexHome), RUNTIME_STATE_FILE_NAME), "utf8"));
|
|
678
|
+
const entries = raw?.sessionFastModes;
|
|
679
|
+
if (entries && typeof entries === "object" && !Array.isArray(entries)) for (const [k, v] of Object.entries(entries)) {
|
|
680
|
+
const normKey = normalizeCodexSessionId(k) ?? (k === "__default__" ? "__default__" : void 0);
|
|
681
|
+
if (normKey !== void 0 && typeof v === "boolean") sessionFastModes.set(normKey, v);
|
|
682
|
+
}
|
|
683
|
+
const parents = raw?.sessionParents;
|
|
684
|
+
if (parents && typeof parents === "object" && !Array.isArray(parents)) for (const [child, parent] of Object.entries(parents)) {
|
|
685
|
+
const childKey = normalizeCodexSessionId(child);
|
|
686
|
+
const parentKey = typeof parent === "string" ? normalizeCodexSessionId(parent) : void 0;
|
|
687
|
+
if (childKey !== void 0 && parentKey !== void 0 && childKey !== parentKey) sessionParents.set(childKey, parentKey);
|
|
658
688
|
}
|
|
659
689
|
} catch {}
|
|
660
|
-
return
|
|
690
|
+
return {
|
|
691
|
+
sessionFastModes,
|
|
692
|
+
sessionParents
|
|
693
|
+
};
|
|
661
694
|
}
|
|
662
|
-
function
|
|
695
|
+
function writePersistedRuntimeState(codexHome, ctx, sessionFastModes, sessionParents) {
|
|
663
696
|
try {
|
|
664
|
-
writeFileAtomicSync(join(resolvePluginCacheDir(codexHome), RUNTIME_STATE_FILE_NAME), `${JSON.stringify({
|
|
697
|
+
writeFileAtomicSync(join(resolvePluginCacheDir(codexHome), RUNTIME_STATE_FILE_NAME), `${JSON.stringify({
|
|
698
|
+
sessionFastModes: Object.fromEntries(sessionFastModes),
|
|
699
|
+
sessionParents: Object.fromEntries(sessionParents)
|
|
700
|
+
}, null, 2)}\n`);
|
|
665
701
|
} catch (error) {
|
|
666
702
|
appendPluginLog(codexHome, ctx, "WARN", "fast_mode.persist_error", { error: safeErrorMessage(error) });
|
|
667
703
|
}
|
|
@@ -669,7 +705,8 @@ function writePersistedSessionFastModes(codexHome, ctx, sessionFastModes) {
|
|
|
669
705
|
const Config = z.object({
|
|
670
706
|
fastMode: z.boolean().default(false).description("新会话默认是否开启 Fast 极速模式(未填时默认 false;单会话可在右下角限额弹窗内独立切换)").volatile(),
|
|
671
707
|
disabledModels: z.array(z.string()).default([...DEFAULT_DISABLED_MODELS]).description("在模型选择器中屏蔽的模型 slug 列表").volatile(),
|
|
672
|
-
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()
|
|
673
710
|
});
|
|
674
711
|
function readVolatile(field, fallback) {
|
|
675
712
|
const value = typeof field?.get === "function" ? field.get() : field;
|
|
@@ -683,7 +720,7 @@ var CodexPiAiAdapter = class extends PiAiAdapter {
|
|
|
683
720
|
}
|
|
684
721
|
modelInfo(snapshot, provider, model) {
|
|
685
722
|
const info = super.modelInfo(snapshot, provider, model);
|
|
686
|
-
const meta = this.#getModelMetadata(model);
|
|
723
|
+
const meta = this.#getModelMetadata(snapshot, model);
|
|
687
724
|
if (meta === void 0) return info;
|
|
688
725
|
const defaultLevel = meta.defaultReasoningLevel;
|
|
689
726
|
const hasDefaultLevel = defaultLevel !== void 0 && info.reasoning !== void 0 && info.reasoning.efforts.some((e) => e.id === defaultLevel);
|
|
@@ -703,34 +740,148 @@ var CodexPiAiAdapter = class extends PiAiAdapter {
|
|
|
703
740
|
function apply(ctx, config = {}) {
|
|
704
741
|
const baseProvider = openaiCodexProvider();
|
|
705
742
|
const baseModels = baseProvider.getModels();
|
|
706
|
-
const retryPolicy = resolveRetryPolicy(void 0, "codex-subscription retryPolicy");
|
|
707
743
|
const getCodexHome = () => resolveCodexHome();
|
|
708
744
|
const getConfigFastMode = () => readVolatile(config.fastMode, false) === true;
|
|
709
745
|
const getDisabledModels = () => readVolatile(config.disabledModels, DEFAULT_DISABLED_MODELS);
|
|
710
746
|
const getContextWindows = () => readVolatile(config.contextWindows, {});
|
|
711
|
-
const
|
|
747
|
+
const getRawRetryPolicy = () => readVolatile(config.retryPolicy, void 0);
|
|
748
|
+
const resolveConfiguredRetryPolicy = () => resolveRetryPolicy(getRawRetryPolicy(), "codex-subscription retryPolicy");
|
|
749
|
+
const { sessionFastModes, sessionParents } = readPersistedRuntimeState(getCodexHome());
|
|
750
|
+
const recordSessionParent = (childKey, parentKey) => {
|
|
751
|
+
if (childKey === "" || parentKey === "" || childKey === parentKey) return false;
|
|
752
|
+
let cursor = parentKey;
|
|
753
|
+
let depth = 0;
|
|
754
|
+
while (cursor !== void 0 && depth < 32) {
|
|
755
|
+
if (cursor === childKey) return false;
|
|
756
|
+
cursor = sessionParents.get(cursor);
|
|
757
|
+
depth += 1;
|
|
758
|
+
}
|
|
759
|
+
if (sessionParents.get(childKey) === parentKey) return false;
|
|
760
|
+
sessionParents.delete(childKey);
|
|
761
|
+
sessionParents.set(childKey, parentKey);
|
|
762
|
+
while (sessionParents.size > MAX_PERSISTED_PARENTS) {
|
|
763
|
+
let victim;
|
|
764
|
+
for (const candidate of sessionParents.keys()) {
|
|
765
|
+
const root = resolveRootSessionId(candidate, sessionParents);
|
|
766
|
+
if (!sessionFastModes.has(root)) {
|
|
767
|
+
victim = candidate;
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const keyToDelete = victim ?? sessionParents.keys().next().value;
|
|
772
|
+
if (keyToDelete === void 0) break;
|
|
773
|
+
sessionParents.delete(keyToDelete);
|
|
774
|
+
}
|
|
775
|
+
return true;
|
|
776
|
+
};
|
|
777
|
+
const findLiveSession = (key) => {
|
|
778
|
+
const sessions = ctx.get?.("sessions");
|
|
779
|
+
if (typeof sessions?.get !== "function") return void 0;
|
|
780
|
+
return sessions.get(key) ?? sessions.get(`session-${key}`);
|
|
781
|
+
};
|
|
782
|
+
const findLiveAgent = (key) => {
|
|
783
|
+
const agents = ctx.get?.("agents");
|
|
784
|
+
if (typeof agents?.get !== "function") return void 0;
|
|
785
|
+
return agents.get(key) ?? agents.get(`session-${key}`);
|
|
786
|
+
};
|
|
787
|
+
const inspectSessionParentFromHost = (sessionLike, agentLike) => {
|
|
788
|
+
const childKey = normalizeCodexSessionId(sessionLike?.id ?? sessionLike?.header?.id ?? agentLike?.id ?? agentLike?.session?.id);
|
|
789
|
+
if (childKey === void 0) return false;
|
|
790
|
+
const header = sessionLike?.header ?? agentLike?.session?.header;
|
|
791
|
+
if (header?.origin === "subagent" && typeof header.parentSession === "string") {
|
|
792
|
+
const parentKey = normalizeCodexSessionId(header.parentSession);
|
|
793
|
+
if (parentKey !== void 0) return recordSessionParent(childKey, parentKey);
|
|
794
|
+
}
|
|
795
|
+
const agentTeams = ctx.get?.("agentTeams");
|
|
796
|
+
if (typeof agentTeams?.tryMembership === "function") {
|
|
797
|
+
const agent = agentLike ?? findLiveAgent(childKey);
|
|
798
|
+
if (agent !== void 0) {
|
|
799
|
+
const membership = agentTeams.tryMembership(agent);
|
|
800
|
+
if (membership?.role === "teammate" && typeof membership.root?.id === "string") {
|
|
801
|
+
const rootKey = normalizeCodexSessionId(membership.root.id);
|
|
802
|
+
if (rootKey !== void 0) return recordSessionParent(childKey, rootKey);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return false;
|
|
807
|
+
};
|
|
808
|
+
const normalizeFastModesToRoots = () => {
|
|
809
|
+
let migrated = false;
|
|
810
|
+
for (const [k, v] of [...sessionFastModes.entries()]) {
|
|
811
|
+
const rootKey = resolveRootSessionId(k, sessionParents);
|
|
812
|
+
if (rootKey !== k) {
|
|
813
|
+
if (!sessionFastModes.has(rootKey)) sessionFastModes.set(rootKey, v);
|
|
814
|
+
sessionFastModes.delete(k);
|
|
815
|
+
migrated = true;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return migrated;
|
|
819
|
+
};
|
|
820
|
+
const syncAllLiveSessionsFromHost = () => {
|
|
821
|
+
let changed = false;
|
|
822
|
+
const sessions = ctx.get?.("sessions");
|
|
823
|
+
if (typeof sessions?.list === "function") {
|
|
824
|
+
for (const s of sessions.list()) if (inspectSessionParentFromHost(s)) changed = true;
|
|
825
|
+
}
|
|
826
|
+
const agents = ctx.get?.("agents");
|
|
827
|
+
if (typeof agents?.list === "function") {
|
|
828
|
+
for (const a of agents.list()) if (inspectSessionParentFromHost(a?.session, a)) changed = true;
|
|
829
|
+
}
|
|
830
|
+
if (normalizeFastModesToRoots()) changed = true;
|
|
831
|
+
return changed;
|
|
832
|
+
};
|
|
833
|
+
const resolveRootSessionKey = (sessionId) => {
|
|
834
|
+
const initiatorId = (ctx.get?.("agents"))?.currentInitiator?.()?.id;
|
|
835
|
+
const startKey = normalizeCodexSessionId(sessionId) ?? normalizeCodexSessionId(initiatorId);
|
|
836
|
+
if (startKey === void 0) return DEFAULT_SESSION_KEY;
|
|
837
|
+
let current = startKey;
|
|
838
|
+
const visited = new Set([current]);
|
|
839
|
+
while (visited.size <= 32) {
|
|
840
|
+
const liveSession = findLiveSession(current);
|
|
841
|
+
const liveAgent = findLiveAgent(current);
|
|
842
|
+
if (liveSession !== void 0 || liveAgent !== void 0) inspectSessionParentFromHost(liveSession, liveAgent);
|
|
843
|
+
const parent = sessionParents.get(current);
|
|
844
|
+
if (parent === void 0 || visited.has(parent)) break;
|
|
845
|
+
visited.add(parent);
|
|
846
|
+
current = parent;
|
|
847
|
+
}
|
|
848
|
+
normalizeFastModesToRoots();
|
|
849
|
+
return current;
|
|
850
|
+
};
|
|
712
851
|
const getEffectiveFastMode = (sessionId) => {
|
|
713
|
-
const
|
|
714
|
-
if (
|
|
715
|
-
if (key === void 0 && sessionFastModes.has(DEFAULT_SESSION_KEY)) return sessionFastModes.get(DEFAULT_SESSION_KEY) === true;
|
|
852
|
+
const rootKey = resolveRootSessionKey(sessionId);
|
|
853
|
+
if (sessionFastModes.has(rootKey)) return sessionFastModes.get(rootKey) === true;
|
|
716
854
|
return getConfigFastMode();
|
|
717
855
|
};
|
|
718
856
|
const setSessionFastMode = (sessionId, nextFastMode) => {
|
|
719
857
|
const codexHome = getCodexHome();
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
sessionFastModes.
|
|
858
|
+
syncAllLiveSessionsFromHost();
|
|
859
|
+
const rootKey = resolveRootSessionKey(sessionId);
|
|
860
|
+
sessionFastModes.delete(rootKey);
|
|
861
|
+
sessionFastModes.set(rootKey, nextFastMode);
|
|
862
|
+
normalizeFastModesToRoots();
|
|
723
863
|
while (sessionFastModes.size > MAX_PERSISTED_SESSIONS) {
|
|
724
864
|
const oldest = sessionFastModes.keys().next().value;
|
|
725
865
|
if (oldest === void 0) break;
|
|
726
866
|
sessionFastModes.delete(oldest);
|
|
727
867
|
}
|
|
728
|
-
|
|
868
|
+
writePersistedRuntimeState(codexHome, ctx, sessionFastModes, sessionParents);
|
|
729
869
|
appendPluginLog(codexHome, ctx, "INFO", "fast_mode.updated", {
|
|
730
|
-
sessionId:
|
|
870
|
+
sessionId: normalizeCodexSessionId(sessionId) ?? "__default__",
|
|
871
|
+
rootSessionId: rootKey,
|
|
731
872
|
fastMode: nextFastMode
|
|
732
873
|
});
|
|
733
874
|
};
|
|
875
|
+
const buildProjectedSessionFastModes = () => {
|
|
876
|
+
syncAllLiveSessionsFromHost();
|
|
877
|
+
const projected = {};
|
|
878
|
+
for (const [rootKey, v] of sessionFastModes) projected[rootKey] = v;
|
|
879
|
+
for (const childKey of [...sessionParents.keys()]) {
|
|
880
|
+
const rootKey = resolveRootSessionId(childKey, sessionParents);
|
|
881
|
+
if (sessionFastModes.has(rootKey)) projected[childKey] = sessionFastModes.get(rootKey) === true;
|
|
882
|
+
}
|
|
883
|
+
return projected;
|
|
884
|
+
};
|
|
734
885
|
const authManager = createAuthManager({
|
|
735
886
|
getCodexHome,
|
|
736
887
|
getClientVersion: () => ensureSnapshot().catalog.clientVersion,
|
|
@@ -740,13 +891,9 @@ function apply(ctx, config = {}) {
|
|
|
740
891
|
let cachedRaw;
|
|
741
892
|
let lastRejectedRaw;
|
|
742
893
|
let rawRevision = 0;
|
|
743
|
-
let
|
|
744
|
-
let
|
|
745
|
-
|
|
746
|
-
models: [],
|
|
747
|
-
metadataById: new Map()
|
|
748
|
-
};
|
|
749
|
-
let currentProfilesMap;
|
|
894
|
+
let activeRetryPolicy = resolveConfiguredRetryPolicy();
|
|
895
|
+
let currentState;
|
|
896
|
+
const catalogByProfiles = new WeakMap();
|
|
750
897
|
const requestTokenAuth = Object.freeze({
|
|
751
898
|
name: "Codex CLI auth.json token",
|
|
752
899
|
async resolve({ credential }) {
|
|
@@ -758,11 +905,12 @@ function apply(ctx, config = {}) {
|
|
|
758
905
|
};
|
|
759
906
|
}
|
|
760
907
|
});
|
|
761
|
-
function wrapOptions(model, options = {}) {
|
|
762
|
-
const { catalog } = ensureSnapshot();
|
|
908
|
+
function wrapOptions(catalog, model, options = {}) {
|
|
763
909
|
const modelMeta = catalog.metadataById.get(model?.id);
|
|
764
910
|
const { temperature: _temperature, sessionId, onPayload, ...rest } = options;
|
|
765
|
-
const
|
|
911
|
+
const initiatorId = (ctx.get?.("agents"))?.currentInitiator?.()?.id;
|
|
912
|
+
const normalizedSessionId = normalizeCodexSessionId(sessionId) ?? normalizeCodexSessionId(initiatorId);
|
|
913
|
+
resolveRootSessionKey(normalizedSessionId);
|
|
766
914
|
return {
|
|
767
915
|
...rest,
|
|
768
916
|
...normalizedSessionId !== void 0 ? { sessionId: normalizedSessionId } : {},
|
|
@@ -777,25 +925,28 @@ function apply(ctx, config = {}) {
|
|
|
777
925
|
resolveCredentials: () => authManager.resolveCredentials(),
|
|
778
926
|
buildHeaders: (authHeaders) => buildCodexHeaders(authHeaders, ensureSnapshot().catalog.clientVersion)
|
|
779
927
|
});
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
+
}
|
|
798
948
|
function ensureSnapshot() {
|
|
949
|
+
const retryPolicy = activeRetryPolicy;
|
|
799
950
|
const codexHome = getCodexHome();
|
|
800
951
|
const cachePath = join(codexHome, "models_cache.json");
|
|
801
952
|
const disabled = getDisabledModels();
|
|
@@ -868,24 +1019,30 @@ function apply(ctx, config = {}) {
|
|
|
868
1019
|
}
|
|
869
1020
|
}
|
|
870
1021
|
}
|
|
871
|
-
const
|
|
1022
|
+
const nextCatalogSig = JSON.stringify([
|
|
872
1023
|
rawRevision,
|
|
873
1024
|
disabled,
|
|
874
1025
|
contextWindows
|
|
875
1026
|
]);
|
|
876
|
-
|
|
877
|
-
|
|
1027
|
+
let nextCatalog = currentState?.catalog;
|
|
1028
|
+
if (nextCatalog === void 0 || nextCatalogSig !== currentState?.catalogSig) {
|
|
1029
|
+
nextCatalog = cachedRaw !== void 0 ? parseModelsCache(cachedRaw, baseModels, disabled, contextWindows) : {
|
|
878
1030
|
clientVersion: DEFAULT_CLIENT_VERSION,
|
|
879
1031
|
models: [],
|
|
880
1032
|
metadataById: new Map()
|
|
881
1033
|
};
|
|
882
|
-
appendPluginLog(codexHome, ctx,
|
|
883
|
-
clientVersion:
|
|
884
|
-
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),
|
|
885
1037
|
disabledModels: disabled,
|
|
886
1038
|
contextWindows
|
|
887
1039
|
});
|
|
888
|
-
|
|
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({
|
|
889
1046
|
provider: PROVIDER,
|
|
890
1047
|
displayName: DISPLAY_NAME,
|
|
891
1048
|
piProvider,
|
|
@@ -898,13 +1055,19 @@ function apply(ctx, config = {}) {
|
|
|
898
1055
|
streamIdleTimeoutMs: STREAM_IDLE_TIMEOUT_MS,
|
|
899
1056
|
cacheRetention: "short",
|
|
900
1057
|
transport: "sse"
|
|
901
|
-
})
|
|
902
|
-
|
|
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
|
+
});
|
|
903
1069
|
}
|
|
904
|
-
return
|
|
905
|
-
catalog: currentCatalog,
|
|
906
|
-
profiles: currentProfilesMap
|
|
907
|
-
};
|
|
1070
|
+
return currentState;
|
|
908
1071
|
}
|
|
909
1072
|
const adapter = new CodexPiAiAdapter({
|
|
910
1073
|
profiles: () => ensureSnapshot().profiles,
|
|
@@ -930,8 +1093,12 @@ function apply(ctx, config = {}) {
|
|
|
930
1093
|
onReplayDegrade: (provider, model, reason) => {
|
|
931
1094
|
console.error(`[dsh-codex-subscription] replay state for "${provider}/${model}" degraded (${reason})`);
|
|
932
1095
|
}
|
|
933
|
-
}, (modelId) =>
|
|
934
|
-
|
|
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);
|
|
935
1102
|
const mountUsageRoute = (connCtx) => {
|
|
936
1103
|
const connection = connCtx.get?.("connection");
|
|
937
1104
|
const registerFetch = connection?.fetch?.register;
|
|
@@ -960,7 +1127,8 @@ function apply(ctx, config = {}) {
|
|
|
960
1127
|
return new Response(JSON.stringify({
|
|
961
1128
|
ok: true,
|
|
962
1129
|
defaultFastMode: getConfigFastMode(),
|
|
963
|
-
sessionFastModes:
|
|
1130
|
+
sessionFastModes: buildProjectedSessionFastModes(),
|
|
1131
|
+
sessionParents: Object.fromEntries(sessionParents)
|
|
964
1132
|
}), {
|
|
965
1133
|
status: 200,
|
|
966
1134
|
headers: { "content-type": "application/json" }
|
|
@@ -971,7 +1139,8 @@ function apply(ctx, config = {}) {
|
|
|
971
1139
|
return new Response(JSON.stringify({
|
|
972
1140
|
...result,
|
|
973
1141
|
defaultFastMode: getConfigFastMode(),
|
|
974
|
-
sessionFastModes:
|
|
1142
|
+
sessionFastModes: buildProjectedSessionFastModes(),
|
|
1143
|
+
sessionParents: Object.fromEntries(sessionParents)
|
|
975
1144
|
}), {
|
|
976
1145
|
status: 200,
|
|
977
1146
|
headers: { "content-type": "application/json" }
|
|
@@ -983,8 +1152,45 @@ function apply(ctx, config = {}) {
|
|
|
983
1152
|
};
|
|
984
1153
|
if (typeof ctx.inject === "function") ctx.inject(["connection"], mountUsageRoute);
|
|
985
1154
|
else mountUsageRoute(ctx);
|
|
1155
|
+
ctx.on?.("session/created", (session) => {
|
|
1156
|
+
if (session && typeof session === "object") {
|
|
1157
|
+
if (inspectSessionParentFromHost(session)) writePersistedRuntimeState(getCodexHome(), ctx, sessionFastModes, sessionParents);
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
ctx.on?.("agent/created", (payload) => {
|
|
1161
|
+
if (payload && typeof payload === "object") {
|
|
1162
|
+
const agent = payload.agent;
|
|
1163
|
+
if (inspectSessionParentFromHost(agent?.session, agent)) writePersistedRuntimeState(getCodexHome(), ctx, sessionFastModes, sessionParents);
|
|
1164
|
+
}
|
|
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
|
+
});
|
|
986
1175
|
ctx.on?.("loader/volatile-update", () => {
|
|
987
|
-
|
|
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;
|
|
988
1194
|
ctx.llm.emitAdaptersUpdated?.();
|
|
989
1195
|
});
|
|
990
1196
|
}
|