@lyogvce/dsh-codex-subscription 0.2.9 → 0.2.10
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 +1 -1
- package/lib/client.js +136 -33
- package/lib/index.d.ts +3 -1
- package/lib/index.js +185 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- 底层复用 DSH 内置的 `PiAiAdapter` 与 `@earendil-works/pi-ai` 的 `openaiCodexProvider()`,固定使用 `sse` 传输(含 `zstd` 请求体压缩、规范化纯 UUID `session-id` / `x-client-request-id` 缓存头、`reasoning.encrypted_content` 签名回放与图片自动缩放卸载)。
|
|
23
23
|
5. **Codex 订阅限额内联展示与 ⚡ Fast 极速模式一键切换(`conversation.input.right`)**
|
|
24
24
|
- 当会话选中 `openai-codex` 模型时,在输入框右下角紧邻模型选择器左侧展示限额胶囊(根据 `wham/usage` 返回的 `limit_window_seconds` 自适应展示 Plus 的 `5h X% · 7d Y%` 双窗口或 Pro 的 `7d X%` 单窗口,内置 60 秒缓存、单飞合并与每轮回复结束自动刷新),点击可展开查看对应窗口进度条、重置倒计时与手动刷新按钮;切换至其他 Provider 模型时自动隐藏。
|
|
25
|
-
- **⚡
|
|
25
|
+
- **⚡ 会话树级 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
26
|
|
|
27
27
|
---
|
|
28
28
|
|
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
|
@@ -17,7 +17,7 @@ interface PluginContextLike {
|
|
|
17
17
|
llm: LlmServiceLike;
|
|
18
18
|
logger?: LoggerLike | undefined;
|
|
19
19
|
get?: (name: string) => unknown;
|
|
20
|
-
on?: (event: string, listener: () => void) => () => void;
|
|
20
|
+
on?: (event: string, listener: (...args: unknown[]) => void) => () => void;
|
|
21
21
|
effect?: (factory: () => void | (() => void), label?: string) => () => void;
|
|
22
22
|
inject?: (services: readonly string[], callback: (ctx: PluginContextLike) => void) => () => void;
|
|
23
23
|
}
|
|
@@ -134,12 +134,14 @@ type CodexQuotaResponse = {
|
|
|
134
134
|
readonly quota: CodexQuotaSnapshot;
|
|
135
135
|
readonly defaultFastMode?: boolean | undefined;
|
|
136
136
|
readonly sessionFastModes?: Readonly<Record<string, boolean>> | undefined;
|
|
137
|
+
readonly sessionParents?: Readonly<Record<string, string>> | undefined;
|
|
137
138
|
} | {
|
|
138
139
|
readonly ok: false;
|
|
139
140
|
readonly error: string;
|
|
140
141
|
readonly quota?: CodexQuotaSnapshot | undefined;
|
|
141
142
|
readonly defaultFastMode?: boolean | undefined;
|
|
142
143
|
readonly sessionFastModes?: Readonly<Record<string, boolean>> | undefined;
|
|
144
|
+
readonly sessionParents?: Readonly<Record<string, string>> | undefined;
|
|
143
145
|
};
|
|
144
146
|
interface UsageFetcherOptions {
|
|
145
147
|
readonly resolveCredentials: () => Promise<CodexResolvedCredentials>;
|
package/lib/index.js
CHANGED
|
@@ -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
|
}
|
|
@@ -708,29 +744,142 @@ function apply(ctx, config = {}) {
|
|
|
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 sessionFastModes =
|
|
747
|
+
const { sessionFastModes, sessionParents } = readPersistedRuntimeState(getCodexHome());
|
|
748
|
+
const recordSessionParent = (childKey, parentKey) => {
|
|
749
|
+
if (childKey === "" || parentKey === "" || childKey === parentKey) return false;
|
|
750
|
+
let cursor = parentKey;
|
|
751
|
+
let depth = 0;
|
|
752
|
+
while (cursor !== void 0 && depth < 32) {
|
|
753
|
+
if (cursor === childKey) return false;
|
|
754
|
+
cursor = sessionParents.get(cursor);
|
|
755
|
+
depth += 1;
|
|
756
|
+
}
|
|
757
|
+
if (sessionParents.get(childKey) === parentKey) return false;
|
|
758
|
+
sessionParents.delete(childKey);
|
|
759
|
+
sessionParents.set(childKey, parentKey);
|
|
760
|
+
while (sessionParents.size > MAX_PERSISTED_PARENTS) {
|
|
761
|
+
let victim;
|
|
762
|
+
for (const candidate of sessionParents.keys()) {
|
|
763
|
+
const root = resolveRootSessionId(candidate, sessionParents);
|
|
764
|
+
if (!sessionFastModes.has(root)) {
|
|
765
|
+
victim = candidate;
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
const keyToDelete = victim ?? sessionParents.keys().next().value;
|
|
770
|
+
if (keyToDelete === void 0) break;
|
|
771
|
+
sessionParents.delete(keyToDelete);
|
|
772
|
+
}
|
|
773
|
+
return true;
|
|
774
|
+
};
|
|
775
|
+
const findLiveSession = (key) => {
|
|
776
|
+
const sessions = ctx.get?.("sessions");
|
|
777
|
+
if (typeof sessions?.get !== "function") return void 0;
|
|
778
|
+
return sessions.get(key) ?? sessions.get(`session-${key}`);
|
|
779
|
+
};
|
|
780
|
+
const findLiveAgent = (key) => {
|
|
781
|
+
const agents = ctx.get?.("agents");
|
|
782
|
+
if (typeof agents?.get !== "function") return void 0;
|
|
783
|
+
return agents.get(key) ?? agents.get(`session-${key}`);
|
|
784
|
+
};
|
|
785
|
+
const inspectSessionParentFromHost = (sessionLike, agentLike) => {
|
|
786
|
+
const childKey = normalizeCodexSessionId(sessionLike?.id ?? sessionLike?.header?.id ?? agentLike?.id ?? agentLike?.session?.id);
|
|
787
|
+
if (childKey === void 0) return false;
|
|
788
|
+
const header = sessionLike?.header ?? agentLike?.session?.header;
|
|
789
|
+
if (header?.origin === "subagent" && typeof header.parentSession === "string") {
|
|
790
|
+
const parentKey = normalizeCodexSessionId(header.parentSession);
|
|
791
|
+
if (parentKey !== void 0) return recordSessionParent(childKey, parentKey);
|
|
792
|
+
}
|
|
793
|
+
const agentTeams = ctx.get?.("agentTeams");
|
|
794
|
+
if (typeof agentTeams?.tryMembership === "function") {
|
|
795
|
+
const agent = agentLike ?? findLiveAgent(childKey);
|
|
796
|
+
if (agent !== void 0) {
|
|
797
|
+
const membership = agentTeams.tryMembership(agent);
|
|
798
|
+
if (membership?.role === "teammate" && typeof membership.root?.id === "string") {
|
|
799
|
+
const rootKey = normalizeCodexSessionId(membership.root.id);
|
|
800
|
+
if (rootKey !== void 0) return recordSessionParent(childKey, rootKey);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return false;
|
|
805
|
+
};
|
|
806
|
+
const normalizeFastModesToRoots = () => {
|
|
807
|
+
let migrated = false;
|
|
808
|
+
for (const [k, v] of [...sessionFastModes.entries()]) {
|
|
809
|
+
const rootKey = resolveRootSessionId(k, sessionParents);
|
|
810
|
+
if (rootKey !== k) {
|
|
811
|
+
if (!sessionFastModes.has(rootKey)) sessionFastModes.set(rootKey, v);
|
|
812
|
+
sessionFastModes.delete(k);
|
|
813
|
+
migrated = true;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return migrated;
|
|
817
|
+
};
|
|
818
|
+
const syncAllLiveSessionsFromHost = () => {
|
|
819
|
+
let changed = false;
|
|
820
|
+
const sessions = ctx.get?.("sessions");
|
|
821
|
+
if (typeof sessions?.list === "function") {
|
|
822
|
+
for (const s of sessions.list()) if (inspectSessionParentFromHost(s)) changed = true;
|
|
823
|
+
}
|
|
824
|
+
const agents = ctx.get?.("agents");
|
|
825
|
+
if (typeof agents?.list === "function") {
|
|
826
|
+
for (const a of agents.list()) if (inspectSessionParentFromHost(a?.session, a)) changed = true;
|
|
827
|
+
}
|
|
828
|
+
if (normalizeFastModesToRoots()) changed = true;
|
|
829
|
+
return changed;
|
|
830
|
+
};
|
|
831
|
+
const resolveRootSessionKey = (sessionId) => {
|
|
832
|
+
const initiatorId = (ctx.get?.("agents"))?.currentInitiator?.()?.id;
|
|
833
|
+
const startKey = normalizeCodexSessionId(sessionId) ?? normalizeCodexSessionId(initiatorId);
|
|
834
|
+
if (startKey === void 0) return DEFAULT_SESSION_KEY;
|
|
835
|
+
let current = startKey;
|
|
836
|
+
const visited = new Set([current]);
|
|
837
|
+
while (visited.size <= 32) {
|
|
838
|
+
const liveSession = findLiveSession(current);
|
|
839
|
+
const liveAgent = findLiveAgent(current);
|
|
840
|
+
if (liveSession !== void 0 || liveAgent !== void 0) inspectSessionParentFromHost(liveSession, liveAgent);
|
|
841
|
+
const parent = sessionParents.get(current);
|
|
842
|
+
if (parent === void 0 || visited.has(parent)) break;
|
|
843
|
+
visited.add(parent);
|
|
844
|
+
current = parent;
|
|
845
|
+
}
|
|
846
|
+
normalizeFastModesToRoots();
|
|
847
|
+
return current;
|
|
848
|
+
};
|
|
712
849
|
const getEffectiveFastMode = (sessionId) => {
|
|
713
|
-
const
|
|
714
|
-
if (
|
|
715
|
-
if (key === void 0 && sessionFastModes.has(DEFAULT_SESSION_KEY)) return sessionFastModes.get(DEFAULT_SESSION_KEY) === true;
|
|
850
|
+
const rootKey = resolveRootSessionKey(sessionId);
|
|
851
|
+
if (sessionFastModes.has(rootKey)) return sessionFastModes.get(rootKey) === true;
|
|
716
852
|
return getConfigFastMode();
|
|
717
853
|
};
|
|
718
854
|
const setSessionFastMode = (sessionId, nextFastMode) => {
|
|
719
855
|
const codexHome = getCodexHome();
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
sessionFastModes.
|
|
856
|
+
syncAllLiveSessionsFromHost();
|
|
857
|
+
const rootKey = resolveRootSessionKey(sessionId);
|
|
858
|
+
sessionFastModes.delete(rootKey);
|
|
859
|
+
sessionFastModes.set(rootKey, nextFastMode);
|
|
860
|
+
normalizeFastModesToRoots();
|
|
723
861
|
while (sessionFastModes.size > MAX_PERSISTED_SESSIONS) {
|
|
724
862
|
const oldest = sessionFastModes.keys().next().value;
|
|
725
863
|
if (oldest === void 0) break;
|
|
726
864
|
sessionFastModes.delete(oldest);
|
|
727
865
|
}
|
|
728
|
-
|
|
866
|
+
writePersistedRuntimeState(codexHome, ctx, sessionFastModes, sessionParents);
|
|
729
867
|
appendPluginLog(codexHome, ctx, "INFO", "fast_mode.updated", {
|
|
730
|
-
sessionId:
|
|
868
|
+
sessionId: normalizeCodexSessionId(sessionId) ?? "__default__",
|
|
869
|
+
rootSessionId: rootKey,
|
|
731
870
|
fastMode: nextFastMode
|
|
732
871
|
});
|
|
733
872
|
};
|
|
873
|
+
const buildProjectedSessionFastModes = () => {
|
|
874
|
+
syncAllLiveSessionsFromHost();
|
|
875
|
+
const projected = {};
|
|
876
|
+
for (const [rootKey, v] of sessionFastModes) projected[rootKey] = v;
|
|
877
|
+
for (const childKey of [...sessionParents.keys()]) {
|
|
878
|
+
const rootKey = resolveRootSessionId(childKey, sessionParents);
|
|
879
|
+
if (sessionFastModes.has(rootKey)) projected[childKey] = sessionFastModes.get(rootKey) === true;
|
|
880
|
+
}
|
|
881
|
+
return projected;
|
|
882
|
+
};
|
|
734
883
|
const authManager = createAuthManager({
|
|
735
884
|
getCodexHome,
|
|
736
885
|
getClientVersion: () => ensureSnapshot().catalog.clientVersion,
|
|
@@ -762,7 +911,9 @@ function apply(ctx, config = {}) {
|
|
|
762
911
|
const { catalog } = ensureSnapshot();
|
|
763
912
|
const modelMeta = catalog.metadataById.get(model?.id);
|
|
764
913
|
const { temperature: _temperature, sessionId, onPayload, ...rest } = options;
|
|
765
|
-
const
|
|
914
|
+
const initiatorId = (ctx.get?.("agents"))?.currentInitiator?.()?.id;
|
|
915
|
+
const normalizedSessionId = normalizeCodexSessionId(sessionId) ?? normalizeCodexSessionId(initiatorId);
|
|
916
|
+
resolveRootSessionKey(normalizedSessionId);
|
|
766
917
|
return {
|
|
767
918
|
...rest,
|
|
768
919
|
...normalizedSessionId !== void 0 ? { sessionId: normalizedSessionId } : {},
|
|
@@ -960,7 +1111,8 @@ function apply(ctx, config = {}) {
|
|
|
960
1111
|
return new Response(JSON.stringify({
|
|
961
1112
|
ok: true,
|
|
962
1113
|
defaultFastMode: getConfigFastMode(),
|
|
963
|
-
sessionFastModes:
|
|
1114
|
+
sessionFastModes: buildProjectedSessionFastModes(),
|
|
1115
|
+
sessionParents: Object.fromEntries(sessionParents)
|
|
964
1116
|
}), {
|
|
965
1117
|
status: 200,
|
|
966
1118
|
headers: { "content-type": "application/json" }
|
|
@@ -971,7 +1123,8 @@ function apply(ctx, config = {}) {
|
|
|
971
1123
|
return new Response(JSON.stringify({
|
|
972
1124
|
...result,
|
|
973
1125
|
defaultFastMode: getConfigFastMode(),
|
|
974
|
-
sessionFastModes:
|
|
1126
|
+
sessionFastModes: buildProjectedSessionFastModes(),
|
|
1127
|
+
sessionParents: Object.fromEntries(sessionParents)
|
|
975
1128
|
}), {
|
|
976
1129
|
status: 200,
|
|
977
1130
|
headers: { "content-type": "application/json" }
|
|
@@ -983,6 +1136,17 @@ function apply(ctx, config = {}) {
|
|
|
983
1136
|
};
|
|
984
1137
|
if (typeof ctx.inject === "function") ctx.inject(["connection"], mountUsageRoute);
|
|
985
1138
|
else mountUsageRoute(ctx);
|
|
1139
|
+
ctx.on?.("session/created", (session) => {
|
|
1140
|
+
if (session && typeof session === "object") {
|
|
1141
|
+
if (inspectSessionParentFromHost(session)) writePersistedRuntimeState(getCodexHome(), ctx, sessionFastModes, sessionParents);
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
ctx.on?.("agent/created", (payload) => {
|
|
1145
|
+
if (payload && typeof payload === "object") {
|
|
1146
|
+
const agent = payload.agent;
|
|
1147
|
+
if (inspectSessionParentFromHost(agent?.session, agent)) writePersistedRuntimeState(getCodexHome(), ctx, sessionFastModes, sessionParents);
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
986
1150
|
ctx.on?.("loader/volatile-update", () => {
|
|
987
1151
|
ensureSnapshot();
|
|
988
1152
|
ctx.llm.emitAdaptersUpdated?.();
|