@lyogvce/dsh-codex-subscription 0.2.8 → 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 +198 -48
- package/lib/index.d.ts +3 -1
- package/lib/index.js +249 -47
- 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,38 +176,108 @@ 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
|
};
|
|
183
|
+
let confirmedSessionFastModes = {};
|
|
184
|
+
let knownSessionParents = {};
|
|
185
|
+
const latestSeqByNode = new Map();
|
|
186
|
+
const pendingBySeq = new Map();
|
|
160
187
|
let inflight = null;
|
|
161
188
|
let inflightForce = false;
|
|
162
189
|
let requestSeq = 0;
|
|
163
190
|
let fastModeSeq = 0;
|
|
191
|
+
let fastMutationEpoch = 0;
|
|
164
192
|
const listeners = new Set();
|
|
165
193
|
function setState(next) {
|
|
166
194
|
state = next;
|
|
167
195
|
for (const listener of listeners) listener();
|
|
168
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
|
+
}
|
|
232
|
+
function deriveSessionFastModes() {
|
|
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
|
+
}
|
|
254
|
+
return merged;
|
|
255
|
+
}
|
|
256
|
+
function parseServerSessionFastModes(raw) {
|
|
257
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
258
|
+
const nextMap = {};
|
|
259
|
+
for (const [k, v] of Object.entries(raw)) if (typeof k === "string" && k !== "" && typeof v === "boolean") nextMap[k] = v;
|
|
260
|
+
return nextMap;
|
|
261
|
+
}
|
|
169
262
|
function mergeFastStateFromPayload(payload, allowSessionReplace) {
|
|
170
263
|
const defaultFastMode = typeof payload?.defaultFastMode === "boolean" ? payload.defaultFastMode : state.defaultFastMode;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
defaultFastMode,
|
|
176
|
-
sessionFastModes: nextMap
|
|
177
|
-
};
|
|
264
|
+
mergeServerSessionParents(payload?.sessionParents);
|
|
265
|
+
if (allowSessionReplace) {
|
|
266
|
+
const parsedMap = parseServerSessionFastModes(payload?.sessionFastModes);
|
|
267
|
+
if (parsedMap !== void 0) confirmedSessionFastModes = parsedMap;
|
|
178
268
|
}
|
|
179
269
|
return {
|
|
180
270
|
defaultFastMode,
|
|
181
|
-
sessionFastModes:
|
|
271
|
+
sessionFastModes: deriveSessionFastModes(),
|
|
272
|
+
sessionParents: knownSessionParents
|
|
182
273
|
};
|
|
183
274
|
}
|
|
184
275
|
async function refresh(force = false) {
|
|
185
276
|
if (!force && state.quota !== null && nowFn() - state.quota.fetchedAt < ttlMs) return;
|
|
186
277
|
if (inflight !== null && (!force || inflightForce)) return inflight;
|
|
187
278
|
const seq = ++requestSeq;
|
|
188
|
-
const
|
|
279
|
+
const canReplaceFastAtStart = pendingBySeq.size === 0;
|
|
280
|
+
const fastEpochAtStart = fastMutationEpoch;
|
|
189
281
|
inflightForce = force;
|
|
190
282
|
setState({
|
|
191
283
|
...state,
|
|
@@ -210,7 +302,7 @@ window.__ModuleLoader__.load({
|
|
|
210
302
|
}
|
|
211
303
|
const payload = await response.json();
|
|
212
304
|
if (seq !== requestSeq) return;
|
|
213
|
-
const fastState = mergeFastStateFromPayload(payload,
|
|
305
|
+
const fastState = mergeFastStateFromPayload(payload, canReplaceFastAtStart && pendingBySeq.size === 0 && fastEpochAtStart === fastMutationEpoch);
|
|
214
306
|
if (payload && payload.ok === true && payload.quota) setState({
|
|
215
307
|
quota: payload.quota,
|
|
216
308
|
...fastState,
|
|
@@ -244,16 +336,39 @@ window.__ModuleLoader__.load({
|
|
|
244
336
|
inflight = task;
|
|
245
337
|
return task;
|
|
246
338
|
}
|
|
247
|
-
async function setFastMode(sessionId, enabled) {
|
|
339
|
+
async function setFastMode(sessionId, enabled, parentSessionId) {
|
|
248
340
|
const nextFastMode = Boolean(enabled);
|
|
249
|
-
const
|
|
341
|
+
const selfKey = normalizeCodexSessionId(sessionId) ?? "__default__";
|
|
342
|
+
const parentKey = normalizeCodexSessionId(parentSessionId);
|
|
343
|
+
if (parentKey !== void 0 && parentKey !== selfKey) recordClientSessionParent(selfKey, parentKey);
|
|
250
344
|
const seq = ++fastModeSeq;
|
|
345
|
+
fastMutationEpoch += 1;
|
|
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, {
|
|
351
|
+
seq,
|
|
352
|
+
selfKey,
|
|
353
|
+
parentKey,
|
|
354
|
+
enabled: nextFastMode
|
|
355
|
+
});
|
|
356
|
+
const rollback = (errorMsg) => {
|
|
357
|
+
pendingBySeq.delete(seq);
|
|
358
|
+
if (isTreeSeqSuperseded(seq, selfKey, parentKey)) return;
|
|
359
|
+
fastMutationEpoch += 1;
|
|
360
|
+
setState({
|
|
361
|
+
...state,
|
|
362
|
+
sessionFastModes: deriveSessionFastModes(),
|
|
363
|
+
sessionParents: knownSessionParents,
|
|
364
|
+
error: errorMsg
|
|
365
|
+
});
|
|
366
|
+
};
|
|
251
367
|
setState({
|
|
252
368
|
...state,
|
|
253
|
-
sessionFastModes:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
369
|
+
sessionFastModes: deriveSessionFastModes(),
|
|
370
|
+
sessionParents: knownSessionParents,
|
|
371
|
+
error: null
|
|
257
372
|
});
|
|
258
373
|
try {
|
|
259
374
|
const response = await fetchFn(USAGE_ROUTE_PATH, {
|
|
@@ -263,17 +378,56 @@ window.__ModuleLoader__.load({
|
|
|
263
378
|
Accept: "application/json"
|
|
264
379
|
},
|
|
265
380
|
body: JSON.stringify({
|
|
266
|
-
sessionId:
|
|
381
|
+
sessionId: selfKey,
|
|
267
382
|
fastMode: nextFastMode
|
|
268
383
|
})
|
|
269
384
|
});
|
|
270
|
-
if (
|
|
385
|
+
if (!response.ok) {
|
|
386
|
+
const errPayload = await response.json().catch(() => null);
|
|
387
|
+
mergeServerSessionParents(errPayload?.sessionParents);
|
|
388
|
+
rollback(typeof errPayload?.error === "string" && errPayload.error !== "" ? errPayload.error : `HTTP ${response.status}`);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
271
391
|
const payload = await response.json();
|
|
272
|
-
|
|
392
|
+
mergeServerSessionParents(payload?.sessionParents);
|
|
393
|
+
if (isTreeSeqSuperseded(seq, selfKey, parentKey)) {
|
|
394
|
+
pendingBySeq.delete(seq);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (!payload || payload.ok === false) {
|
|
398
|
+
rollback(typeof payload?.error === "string" && payload.error !== "" ? payload.error : "Invalid response");
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
fastMutationEpoch += 1;
|
|
402
|
+
pendingBySeq.delete(seq);
|
|
403
|
+
const actualRootKey = resolveRootSessionId(selfKey, knownSessionParents, parentKey);
|
|
404
|
+
latestSeqByNode.set(actualRootKey, Math.max(latestSeqByNode.get(actualRootKey) ?? 0, seq));
|
|
405
|
+
const parsedMap = parseServerSessionFastModes(payload.sessionFastModes);
|
|
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;
|
|
418
|
+
const defaultFastMode = typeof payload.defaultFastMode === "boolean" ? payload.defaultFastMode : state.defaultFastMode;
|
|
419
|
+
setState({
|
|
273
420
|
...state,
|
|
274
|
-
|
|
421
|
+
defaultFastMode,
|
|
422
|
+
sessionFastModes: deriveSessionFastModes(),
|
|
423
|
+
sessionParents: knownSessionParents,
|
|
424
|
+
error: null
|
|
275
425
|
});
|
|
276
|
-
} catch {
|
|
426
|
+
} catch (error) {
|
|
427
|
+
rollback(safeErrorMessage(error));
|
|
428
|
+
} finally {
|
|
429
|
+
pendingBySeq.delete(seq);
|
|
430
|
+
}
|
|
277
431
|
}
|
|
278
432
|
return {
|
|
279
433
|
getSnapshot: () => state,
|
|
@@ -359,12 +513,26 @@ window.__ModuleLoader__.load({
|
|
|
359
513
|
getSnapshot: () => null,
|
|
360
514
|
subscribe: () => () => {}
|
|
361
515
|
};
|
|
516
|
+
function renderFastBolt(size) {
|
|
517
|
+
return h("svg", {
|
|
518
|
+
className: "dsh-codex-quota-fast-bolt",
|
|
519
|
+
viewBox: "0 0 12 12",
|
|
520
|
+
width: size,
|
|
521
|
+
height: size,
|
|
522
|
+
"aria-hidden": true
|
|
523
|
+
}, h("path", {
|
|
524
|
+
d: "M6.8 1L2.5 6.8h3.1L4.8 11l4.7-5.8H6.4L6.8 1z",
|
|
525
|
+
fill: "currentColor"
|
|
526
|
+
}));
|
|
527
|
+
}
|
|
362
528
|
function CodexQuotaPill(props) {
|
|
363
529
|
const { sessionId, quotaStore, getModelStore, useProjection, useSession, t, getLocale } = props;
|
|
364
530
|
const modelStore = getModelStore(sessionId);
|
|
365
531
|
const directorySnap = React.useSyncExternalStore(modelStore.subscribe, modelStore.getSnapshot);
|
|
366
532
|
const projectedSelection = typeof useProjection === "function" ? useProjection("modelSelection") : void 0;
|
|
367
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;
|
|
368
536
|
const isCodex = isCodexProvider(resolveActiveProvider(directorySnap, projectedSelection));
|
|
369
537
|
const quotaSnap = React.useSyncExternalStore(quotaStore.subscribe, quotaStore.getSnapshot);
|
|
370
538
|
const [open, setOpen] = React.useState(false);
|
|
@@ -424,7 +592,7 @@ window.__ModuleLoader__.load({
|
|
|
424
592
|
})), h("div", { className: "dsh-codex-quota-meta" }, h("span", null, `${t("quota.remainPrefix")}${remainPercent}%`), countdown !== "" ? h("span", null, countdown) : null));
|
|
425
593
|
};
|
|
426
594
|
const quota = quotaSnap.quota;
|
|
427
|
-
const isFastMode = resolveSessionFastMode(quotaSnap, sessionId);
|
|
595
|
+
const isFastMode = resolveSessionFastMode(quotaSnap, sessionId, parentSessionId);
|
|
428
596
|
const secondaryReached = Boolean(quota?.secondary && quota.secondary.usedPercent >= 100);
|
|
429
597
|
const primaryReached = Boolean(quota?.limitReached && !secondaryReached);
|
|
430
598
|
return h("span", {
|
|
@@ -462,16 +630,7 @@ window.__ModuleLoader__.load({
|
|
|
462
630
|
r: 5.5,
|
|
463
631
|
strokeDasharray: ringDash,
|
|
464
632
|
transform: "rotate(-90 7 7)"
|
|
465
|
-
})), isFastMode ? h("
|
|
466
|
-
className: "dsh-codex-quota-fast-bolt",
|
|
467
|
-
viewBox: "0 0 12 12",
|
|
468
|
-
width: 11,
|
|
469
|
-
height: 11,
|
|
470
|
-
"aria-hidden": true
|
|
471
|
-
}, h("path", {
|
|
472
|
-
d: "M6.8 1L2.5 6.8h3.1L4.8 11l4.7-5.8H6.4L6.8 1z",
|
|
473
|
-
fill: "currentColor"
|
|
474
|
-
})) : null, h("span", { className: "dsh-codex-quota-text" }, pillText)), open ? h("div", {
|
|
633
|
+
})), isFastMode ? renderFastBolt(11) : null, h("span", { className: "dsh-codex-quota-text" }, pillText)), open ? h("div", {
|
|
475
634
|
className: "dsh-codex-quota-popover",
|
|
476
635
|
role: "dialog",
|
|
477
636
|
"aria-label": t("quota.title"),
|
|
@@ -485,16 +644,7 @@ window.__ModuleLoader__.load({
|
|
|
485
644
|
onClick: () => {
|
|
486
645
|
quotaStore.refresh(true);
|
|
487
646
|
}
|
|
488
|
-
}, quotaSnap.loading ? t("quota.refreshing") : t("quota.refresh"))), quota !== null ? renderWindowSection(formatWindowTitle(quota.primary.windowSeconds, locale, DEFAULT_PRIMARY_WINDOW_SECONDS), quota.primary, primaryReached) : quotaSnap.loading || quotaSnap.error === null ? h("div", { className: "dsh-codex-quota-meta" }, t("quota.loading")) : null, quota?.secondary !== null && quota?.secondary !== void 0 ? renderWindowSection(formatWindowTitle(quota.secondary.windowSeconds, locale, DEFAULT_SECONDARY_WINDOW_SECONDS), quota.secondary, secondaryReached) : null, h("div", { className: "dsh-codex-fast-row" }, h("div", { className: "dsh-codex-fast-info" }, h("div", { className: "dsh-codex-fast-title" }, h("
|
|
489
|
-
className: "dsh-codex-quota-fast-bolt",
|
|
490
|
-
viewBox: "0 0 12 12",
|
|
491
|
-
width: 12,
|
|
492
|
-
height: 12,
|
|
493
|
-
"aria-hidden": true
|
|
494
|
-
}, h("path", {
|
|
495
|
-
d: "M6.8 1L2.5 6.8h3.1L4.8 11l4.7-5.8H6.4L6.8 1z",
|
|
496
|
-
fill: "currentColor"
|
|
497
|
-
})), h("span", null, t("quota.fastTitle"))), h("div", { className: "dsh-codex-fast-desc" }, t("quota.fastDesc"))), h("button", {
|
|
647
|
+
}, quotaSnap.loading ? t("quota.refreshing") : t("quota.refresh"))), quota !== null ? renderWindowSection(formatWindowTitle(quota.primary.windowSeconds, locale, DEFAULT_PRIMARY_WINDOW_SECONDS), quota.primary, primaryReached) : quotaSnap.loading || quotaSnap.error === null ? h("div", { className: "dsh-codex-quota-meta" }, t("quota.loading")) : null, quota?.secondary !== null && quota?.secondary !== void 0 ? renderWindowSection(formatWindowTitle(quota.secondary.windowSeconds, locale, DEFAULT_SECONDARY_WINDOW_SECONDS), quota.secondary, secondaryReached) : null, h("div", { className: "dsh-codex-fast-row" }, h("div", { className: "dsh-codex-fast-info" }, h("div", { className: "dsh-codex-fast-title" }, renderFastBolt(12), h("span", null, t("quota.fastTitle"))), h("div", { className: "dsh-codex-fast-desc" }, t("quota.fastDesc"))), h("button", {
|
|
498
648
|
type: "button",
|
|
499
649
|
role: "switch",
|
|
500
650
|
className: "dsh-codex-fast-switch",
|
|
@@ -502,7 +652,7 @@ window.__ModuleLoader__.load({
|
|
|
502
652
|
"aria-label": t("quota.fastTitle"),
|
|
503
653
|
"data-checked": isFastMode ? "true" : "false",
|
|
504
654
|
onClick: () => {
|
|
505
|
-
quotaStore.setFastMode(sessionId, !isFastMode);
|
|
655
|
+
quotaStore.setFastMode(sessionId, !isFastMode, parentSessionId);
|
|
506
656
|
}
|
|
507
657
|
}, h("span", { className: "dsh-codex-fast-switch-thumb" }))), quotaSnap.error !== null ? h("div", { className: "dsh-codex-quota-error" }, quotaSnap.error) : null) : null);
|
|
508
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",
|
|
@@ -35,6 +36,8 @@ const DEFAULT_DISABLED_MODELS = Object.freeze([
|
|
|
35
36
|
function parseRawModelsCache(rawText) {
|
|
36
37
|
const parsed = JSON.parse(rawText);
|
|
37
38
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new SyntaxError("models_cache.json root must be a JSON object");
|
|
39
|
+
const models = parsed.models;
|
|
40
|
+
if (!Array.isArray(models) || !models.some((entry) => entry !== null && typeof entry === "object" && typeof entry.slug === "string" && entry.slug.trim() !== "" && entry.visibility === "list")) throw new SyntaxError("models_cache.json must contain at least one visible model entry");
|
|
38
41
|
return parsed;
|
|
39
42
|
}
|
|
40
43
|
function normalizeCodexSessionId(sessionId) {
|
|
@@ -42,6 +45,26 @@ function normalizeCodexSessionId(sessionId) {
|
|
|
42
45
|
const stripped = sessionId.trim().replace(/^session-/iu, "").trim();
|
|
43
46
|
return stripped.length > 0 ? stripped : void 0;
|
|
44
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
|
+
}
|
|
45
68
|
function isSameModelsCatalogPayload(a, b) {
|
|
46
69
|
if (!a || !b) return false;
|
|
47
70
|
const { fetched_at: _fa, ...restA } = a;
|
|
@@ -94,7 +117,7 @@ function resolveFastServiceTier(entry) {
|
|
|
94
117
|
if (entry.service_tiers.length === 0 && Array.isArray(entry.additional_speed_tiers)) return entry.additional_speed_tiers.some((t) => typeof t === "string" && t.trim().toLowerCase() === "fast") ? "priority" : void 0;
|
|
95
118
|
return;
|
|
96
119
|
}
|
|
97
|
-
if (Array.isArray(entry.additional_speed_tiers)
|
|
120
|
+
if (Array.isArray(entry.additional_speed_tiers)) return entry.additional_speed_tiers.some((t) => typeof t === "string" && t.trim().toLowerCase() === "fast") ? "priority" : void 0;
|
|
98
121
|
return "priority";
|
|
99
122
|
}
|
|
100
123
|
function parseModelsCache(rawCache, baseModels, disabledModels = [], contextWindows = {}) {
|
|
@@ -218,7 +241,7 @@ function sanitizeCodexPayload(payload, modelMeta, fastMode = false) {
|
|
|
218
241
|
const tier = modelMeta === void 0 ? "priority" : modelMeta.fastServiceTier;
|
|
219
242
|
if (typeof tier === "string" && tier !== "") cleaned.service_tier = tier;
|
|
220
243
|
else delete cleaned.service_tier;
|
|
221
|
-
} else
|
|
244
|
+
} else delete cleaned.service_tier;
|
|
222
245
|
const currentText = cleaned.text && typeof cleaned.text === "object" && !Array.isArray(cleaned.text) ? { ...cleaned.text } : void 0;
|
|
223
246
|
if (modelMeta?.supportVerbosity === false) {
|
|
224
247
|
if (currentText !== void 0) {
|
|
@@ -587,10 +610,16 @@ const REQUEST_IMAGE_PIXEL_BUDGET = 4194304;
|
|
|
587
610
|
const REQUEST_IMAGE_MAX_BYTES = 1048576;
|
|
588
611
|
const MAX_LOG_FILE_BYTES = 524288;
|
|
589
612
|
const MAX_PERSISTED_SESSIONS = 200;
|
|
590
|
-
const
|
|
613
|
+
const MAX_PERSISTED_PARENTS = 1e3;
|
|
591
614
|
function resolvePluginCacheDir(codexHome) {
|
|
592
615
|
return join(codexHome, PLUGIN_CACHE_DIR_NAME);
|
|
593
616
|
}
|
|
617
|
+
function sliceUtf8Prefix(buf, maxBytes) {
|
|
618
|
+
if (buf.length <= maxBytes) return buf;
|
|
619
|
+
let end = maxBytes;
|
|
620
|
+
while (end > 0 && (buf[end] & 192) === 128) end -= 1;
|
|
621
|
+
return buf.subarray(0, end);
|
|
622
|
+
}
|
|
594
623
|
function appendPluginLog(codexHome, ctx, level, event, detail) {
|
|
595
624
|
const line = `[${new Date().toISOString()}] [${level}] ${event} ${JSON.stringify(detail)}\n`;
|
|
596
625
|
if (level === "ERROR") {
|
|
@@ -598,6 +627,7 @@ function appendPluginLog(codexHome, ctx, level, event, detail) {
|
|
|
598
627
|
else console.error(`[dsh-codex-subscription] ${event}: ${JSON.stringify(detail)}`);
|
|
599
628
|
} else if (level === "WARN") {
|
|
600
629
|
if (typeof ctx.logger?.warn === "function") ctx.logger.warn(`[dsh-codex-subscription] ${event}: ${JSON.stringify(detail)}`);
|
|
630
|
+
else console.warn(`[dsh-codex-subscription] ${event}: ${JSON.stringify(detail)}`);
|
|
601
631
|
}
|
|
602
632
|
try {
|
|
603
633
|
const cacheDir = resolvePluginCacheDir(codexHome);
|
|
@@ -606,18 +636,24 @@ function appendPluginLog(codexHome, ctx, level, event, detail) {
|
|
|
606
636
|
mode: 448
|
|
607
637
|
});
|
|
608
638
|
const logPath = join(cacheDir, LOG_FILE_NAME);
|
|
639
|
+
const rawLineBuf = Buffer.from(line, "utf8");
|
|
640
|
+
const lineBuf = rawLineBuf.length <= MAX_LOG_FILE_BYTES ? rawLineBuf : Buffer.concat([sliceUtf8Prefix(rawLineBuf, 524287), Buffer.from("\n", "utf8")]);
|
|
641
|
+
let st;
|
|
609
642
|
try {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
643
|
+
st = statSync(logPath);
|
|
644
|
+
} catch {
|
|
645
|
+
st = void 0;
|
|
646
|
+
}
|
|
647
|
+
if (st !== void 0 && st.size + lineBuf.length > MAX_LOG_FILE_BYTES) {
|
|
648
|
+
const buf = readFileSync(logPath);
|
|
649
|
+
const keepBudget = Math.max(0, Math.floor(MAX_LOG_FILE_BYTES / 2) - lineBuf.length);
|
|
650
|
+
const startOffset = Math.max(0, buf.length - keepBudget);
|
|
651
|
+
const newlineIdx = keepBudget > 0 ? buf.indexOf(10, startOffset) : -1;
|
|
652
|
+
const tail = newlineIdx >= 0 && newlineIdx + 1 < buf.length ? buf.subarray(newlineIdx + 1) : Buffer.alloc(0);
|
|
653
|
+
writeFileAtomicSync(logPath, Buffer.concat([tail, lineBuf]));
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
appendFileSync(logPath, lineBuf, { mode: 384 });
|
|
621
657
|
} catch {}
|
|
622
658
|
}
|
|
623
659
|
function readBackupCache(codexHome) {
|
|
@@ -627,25 +663,44 @@ function readBackupCache(codexHome) {
|
|
|
627
663
|
return;
|
|
628
664
|
}
|
|
629
665
|
}
|
|
630
|
-
function writeBackupCache(codexHome, raw) {
|
|
666
|
+
function writeBackupCache(codexHome, ctx, raw) {
|
|
631
667
|
try {
|
|
632
668
|
writeFileAtomicSync(join(resolvePluginCacheDir(codexHome), BACKUP_CACHE_FILE_NAME), `${JSON.stringify(raw, null, 2)}\n`);
|
|
633
|
-
} catch {
|
|
669
|
+
} catch (error) {
|
|
670
|
+
appendPluginLog(codexHome, ctx, "WARN", "catalog.backup_write_error", { error: safeErrorMessage(error) });
|
|
671
|
+
}
|
|
634
672
|
}
|
|
635
|
-
function
|
|
636
|
-
const
|
|
673
|
+
function readPersistedRuntimeState(codexHome) {
|
|
674
|
+
const sessionFastModes = new Map();
|
|
675
|
+
const sessionParents = new Map();
|
|
637
676
|
try {
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
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);
|
|
641
688
|
}
|
|
642
689
|
} catch {}
|
|
643
|
-
return
|
|
690
|
+
return {
|
|
691
|
+
sessionFastModes,
|
|
692
|
+
sessionParents
|
|
693
|
+
};
|
|
644
694
|
}
|
|
645
|
-
function
|
|
695
|
+
function writePersistedRuntimeState(codexHome, ctx, sessionFastModes, sessionParents) {
|
|
646
696
|
try {
|
|
647
|
-
writeFileAtomicSync(join(resolvePluginCacheDir(codexHome), RUNTIME_STATE_FILE_NAME), `${JSON.stringify({
|
|
648
|
-
|
|
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`);
|
|
701
|
+
} catch (error) {
|
|
702
|
+
appendPluginLog(codexHome, ctx, "WARN", "fast_mode.persist_error", { error: safeErrorMessage(error) });
|
|
703
|
+
}
|
|
649
704
|
}
|
|
650
705
|
const Config = z.object({
|
|
651
706
|
fastMode: z.boolean().default(false).description("新会话默认是否开启 Fast 极速模式(未填时默认 false;单会话可在右下角限额弹窗内独立切换)").volatile(),
|
|
@@ -689,29 +744,142 @@ function apply(ctx, config = {}) {
|
|
|
689
744
|
const getConfigFastMode = () => readVolatile(config.fastMode, false) === true;
|
|
690
745
|
const getDisabledModels = () => readVolatile(config.disabledModels, DEFAULT_DISABLED_MODELS);
|
|
691
746
|
const getContextWindows = () => readVolatile(config.contextWindows, {});
|
|
692
|
-
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
|
+
};
|
|
693
849
|
const getEffectiveFastMode = (sessionId) => {
|
|
694
|
-
const
|
|
695
|
-
if (
|
|
696
|
-
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;
|
|
697
852
|
return getConfigFastMode();
|
|
698
853
|
};
|
|
699
854
|
const setSessionFastMode = (sessionId, nextFastMode) => {
|
|
700
855
|
const codexHome = getCodexHome();
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
sessionFastModes.
|
|
856
|
+
syncAllLiveSessionsFromHost();
|
|
857
|
+
const rootKey = resolveRootSessionKey(sessionId);
|
|
858
|
+
sessionFastModes.delete(rootKey);
|
|
859
|
+
sessionFastModes.set(rootKey, nextFastMode);
|
|
860
|
+
normalizeFastModesToRoots();
|
|
704
861
|
while (sessionFastModes.size > MAX_PERSISTED_SESSIONS) {
|
|
705
862
|
const oldest = sessionFastModes.keys().next().value;
|
|
706
863
|
if (oldest === void 0) break;
|
|
707
864
|
sessionFastModes.delete(oldest);
|
|
708
865
|
}
|
|
709
|
-
|
|
866
|
+
writePersistedRuntimeState(codexHome, ctx, sessionFastModes, sessionParents);
|
|
710
867
|
appendPluginLog(codexHome, ctx, "INFO", "fast_mode.updated", {
|
|
711
|
-
sessionId:
|
|
868
|
+
sessionId: normalizeCodexSessionId(sessionId) ?? "__default__",
|
|
869
|
+
rootSessionId: rootKey,
|
|
712
870
|
fastMode: nextFastMode
|
|
713
871
|
});
|
|
714
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
|
+
};
|
|
715
883
|
const authManager = createAuthManager({
|
|
716
884
|
getCodexHome,
|
|
717
885
|
getClientVersion: () => ensureSnapshot().catalog.clientVersion,
|
|
@@ -719,6 +887,7 @@ function apply(ctx, config = {}) {
|
|
|
719
887
|
});
|
|
720
888
|
let fileSignature;
|
|
721
889
|
let cachedRaw;
|
|
890
|
+
let lastRejectedRaw;
|
|
722
891
|
let rawRevision = 0;
|
|
723
892
|
let snapshotSignature;
|
|
724
893
|
let currentCatalog = {
|
|
@@ -742,7 +911,9 @@ function apply(ctx, config = {}) {
|
|
|
742
911
|
const { catalog } = ensureSnapshot();
|
|
743
912
|
const modelMeta = catalog.metadataById.get(model?.id);
|
|
744
913
|
const { temperature: _temperature, sessionId, onPayload, ...rest } = options;
|
|
745
|
-
const
|
|
914
|
+
const initiatorId = (ctx.get?.("agents"))?.currentInitiator?.()?.id;
|
|
915
|
+
const normalizedSessionId = normalizeCodexSessionId(sessionId) ?? normalizeCodexSessionId(initiatorId);
|
|
916
|
+
resolveRootSessionKey(normalizedSessionId);
|
|
746
917
|
return {
|
|
747
918
|
...rest,
|
|
748
919
|
...normalizedSessionId !== void 0 ? { sessionId: normalizedSessionId } : {},
|
|
@@ -782,6 +953,7 @@ function apply(ctx, config = {}) {
|
|
|
782
953
|
const contextWindows = getContextWindows();
|
|
783
954
|
const setActiveRaw = (next) => {
|
|
784
955
|
cachedRaw = next;
|
|
956
|
+
lastRejectedRaw = void 0;
|
|
785
957
|
rawRevision += 1;
|
|
786
958
|
};
|
|
787
959
|
const tryRestoreFromBackup = (extra, minVersion) => {
|
|
@@ -820,16 +992,20 @@ function apply(ctx, config = {}) {
|
|
|
820
992
|
fileSignature = nextFileSig;
|
|
821
993
|
try {
|
|
822
994
|
const incomingRaw = parseRawModelsCache(readFileSync(cachePath, "utf8"));
|
|
823
|
-
if (cachedRaw !== void 0 && compareClientVersions(incomingRaw.client_version, cachedRaw.client_version) < 0)
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
995
|
+
if (cachedRaw !== void 0 && compareClientVersions(incomingRaw.client_version, cachedRaw.client_version) < 0) {
|
|
996
|
+
if (!isSameModelsCatalogPayload(lastRejectedRaw, incomingRaw)) {
|
|
997
|
+
lastRejectedRaw = incomingRaw;
|
|
998
|
+
appendPluginLog(codexHome, ctx, "WARN", "catalog.downgrade_rejected", {
|
|
999
|
+
cachePath,
|
|
1000
|
+
keptVersion: cachedRaw.client_version ?? "0.163.0",
|
|
1001
|
+
rejectedVersion: incomingRaw.client_version ?? "unknown",
|
|
1002
|
+
incomingFetchedAt: incomingRaw.fetched_at ?? null
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
} else if (cachedRaw !== void 0 || !tryRestoreFromBackup({ diskVersion: incomingRaw.client_version ?? "unknown" }, incomingRaw.client_version)) {
|
|
830
1006
|
if (!isSameModelsCatalogPayload(cachedRaw, incomingRaw)) {
|
|
831
1007
|
setActiveRaw(incomingRaw);
|
|
832
|
-
writeBackupCache(codexHome, incomingRaw);
|
|
1008
|
+
writeBackupCache(codexHome, ctx, incomingRaw);
|
|
833
1009
|
appendPluginLog(codexHome, ctx, "INFO", "catalog.load", {
|
|
834
1010
|
cachePath,
|
|
835
1011
|
clientVersion: incomingRaw.client_version ?? "0.163.0",
|
|
@@ -917,12 +1093,26 @@ function apply(ctx, config = {}) {
|
|
|
917
1093
|
requestBody: "buffered",
|
|
918
1094
|
fetch: async (request) => {
|
|
919
1095
|
if (request.method === "POST") {
|
|
920
|
-
|
|
921
|
-
|
|
1096
|
+
let body;
|
|
1097
|
+
try {
|
|
1098
|
+
body = await request.json();
|
|
1099
|
+
} catch {
|
|
1100
|
+
body = null;
|
|
1101
|
+
}
|
|
1102
|
+
const record = body !== null && typeof body === "object" && !Array.isArray(body) ? body : null;
|
|
1103
|
+
if (record === null || typeof record.fastMode !== "boolean" || record.sessionId !== void 0 && typeof record.sessionId !== "string") return new Response(JSON.stringify({
|
|
1104
|
+
ok: false,
|
|
1105
|
+
error: "Invalid request body: boolean fastMode is required"
|
|
1106
|
+
}), {
|
|
1107
|
+
status: 400,
|
|
1108
|
+
headers: { "content-type": "application/json" }
|
|
1109
|
+
});
|
|
1110
|
+
setSessionFastMode(record.sessionId, record.fastMode);
|
|
922
1111
|
return new Response(JSON.stringify({
|
|
923
1112
|
ok: true,
|
|
924
1113
|
defaultFastMode: getConfigFastMode(),
|
|
925
|
-
sessionFastModes:
|
|
1114
|
+
sessionFastModes: buildProjectedSessionFastModes(),
|
|
1115
|
+
sessionParents: Object.fromEntries(sessionParents)
|
|
926
1116
|
}), {
|
|
927
1117
|
status: 200,
|
|
928
1118
|
headers: { "content-type": "application/json" }
|
|
@@ -933,7 +1123,8 @@ function apply(ctx, config = {}) {
|
|
|
933
1123
|
return new Response(JSON.stringify({
|
|
934
1124
|
...result,
|
|
935
1125
|
defaultFastMode: getConfigFastMode(),
|
|
936
|
-
sessionFastModes:
|
|
1126
|
+
sessionFastModes: buildProjectedSessionFastModes(),
|
|
1127
|
+
sessionParents: Object.fromEntries(sessionParents)
|
|
937
1128
|
}), {
|
|
938
1129
|
status: 200,
|
|
939
1130
|
headers: { "content-type": "application/json" }
|
|
@@ -945,6 +1136,17 @@ function apply(ctx, config = {}) {
|
|
|
945
1136
|
};
|
|
946
1137
|
if (typeof ctx.inject === "function") ctx.inject(["connection"], mountUsageRoute);
|
|
947
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
|
+
});
|
|
948
1150
|
ctx.on?.("loader/volatile-update", () => {
|
|
949
1151
|
ensureSnapshot();
|
|
950
1152
|
ctx.llm.emitAdaptersUpdated?.();
|