@lyogvce/dsh-codex-subscription 0.2.7 → 0.2.9
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 +121 -36
- package/lib/index.d.ts +5 -3
- package/lib/index.js +107 -57
- 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
|
-
- **⚡ Fast 极速模式(`service_tier: "priority"`,最高 2x
|
|
25
|
+
- **⚡ 会话级 Fast 极速模式(`service_tier: "priority"`,最高 2x 推理速度)**:每个会话可在限额弹窗底部独立开关 Fast 模式(互不影响,新会话默认继承插件设置中的 `fastMode`,未配置时默认 `false` 关闭),状态自动持久化至 `~/.codex/cache/dsh-codex-subscription/runtime-state.json`。开启后当前会话胶囊显示闪电标识 `⚡`,并在该会话请求体中自动注入 `"service_tier": "priority"`。
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
package/lib/client.js
CHANGED
|
@@ -13,6 +13,11 @@ window.__ModuleLoader__.load({
|
|
|
13
13
|
"gpt-5.6-terra",
|
|
14
14
|
"gpt-5.6-luna"
|
|
15
15
|
]);
|
|
16
|
+
function normalizeCodexSessionId(sessionId) {
|
|
17
|
+
if (typeof sessionId !== "string") return void 0;
|
|
18
|
+
const stripped = sessionId.trim().replace(/^session-/iu, "").trim();
|
|
19
|
+
return stripped.length > 0 ? stripped : void 0;
|
|
20
|
+
}
|
|
16
21
|
//#endregion
|
|
17
22
|
//#region src/usage.ts
|
|
18
23
|
function safeErrorMessage(error) {
|
|
@@ -25,8 +30,16 @@ window.__ModuleLoader__.load({
|
|
|
25
30
|
//#endregion
|
|
26
31
|
//#region src/client/quota.ts
|
|
27
32
|
const RING_CIRCUMFERENCE = 2 * Math.PI * 5.5;
|
|
33
|
+
const DEFAULT_SESSION_KEY = "__default__";
|
|
28
34
|
const DEFAULT_PRIMARY_WINDOW_SECONDS = 18e3;
|
|
29
35
|
const DEFAULT_SECONDARY_WINDOW_SECONDS = 604800;
|
|
36
|
+
function resolveSessionFastMode(snapshot, sessionId) {
|
|
37
|
+
if (!snapshot) return false;
|
|
38
|
+
const key = normalizeCodexSessionId(sessionId) ?? DEFAULT_SESSION_KEY;
|
|
39
|
+
const sessionVal = snapshot.sessionFastModes?.[key];
|
|
40
|
+
if (typeof sessionVal === "boolean") return sessionVal;
|
|
41
|
+
return snapshot.defaultFastMode === true;
|
|
42
|
+
}
|
|
30
43
|
function resolveActiveProvider(directorySnapshot, projectedSelection) {
|
|
31
44
|
const dir = directorySnapshot && typeof directorySnapshot === "object" ? directorySnapshot : void 0;
|
|
32
45
|
const proj = projectedSelection && typeof projectedSelection === "object" ? projectedSelection : void 0;
|
|
@@ -139,24 +152,54 @@ window.__ModuleLoader__.load({
|
|
|
139
152
|
const ttlMs = options.ttlMs ?? 6e4;
|
|
140
153
|
let state = {
|
|
141
154
|
quota: null,
|
|
142
|
-
|
|
155
|
+
defaultFastMode: false,
|
|
156
|
+
sessionFastModes: {},
|
|
143
157
|
loading: false,
|
|
144
158
|
error: null
|
|
145
159
|
};
|
|
160
|
+
let confirmedSessionFastModes = {};
|
|
161
|
+
let lastConfirmedFastSeq = 0;
|
|
162
|
+
const latestSeqBySession = new Map();
|
|
163
|
+
const pendingBySession = new Map();
|
|
146
164
|
let inflight = null;
|
|
147
165
|
let inflightForce = false;
|
|
148
166
|
let requestSeq = 0;
|
|
149
167
|
let fastModeSeq = 0;
|
|
168
|
+
let fastMutationEpoch = 0;
|
|
150
169
|
const listeners = new Set();
|
|
151
170
|
function setState(next) {
|
|
152
171
|
state = next;
|
|
153
172
|
for (const listener of listeners) listener();
|
|
154
173
|
}
|
|
174
|
+
function deriveSessionFastModes() {
|
|
175
|
+
if (pendingBySession.size === 0) return confirmedSessionFastModes;
|
|
176
|
+
const merged = { ...confirmedSessionFastModes };
|
|
177
|
+
for (const [k, pending] of pendingBySession) merged[k] = pending.enabled;
|
|
178
|
+
return merged;
|
|
179
|
+
}
|
|
180
|
+
function parseServerSessionFastModes(raw) {
|
|
181
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
182
|
+
const nextMap = {};
|
|
183
|
+
for (const [k, v] of Object.entries(raw)) if (typeof k === "string" && k !== "" && typeof v === "boolean") nextMap[k] = v;
|
|
184
|
+
return nextMap;
|
|
185
|
+
}
|
|
186
|
+
function mergeFastStateFromPayload(payload, allowSessionReplace) {
|
|
187
|
+
const defaultFastMode = typeof payload?.defaultFastMode === "boolean" ? payload.defaultFastMode : state.defaultFastMode;
|
|
188
|
+
if (allowSessionReplace) {
|
|
189
|
+
const parsedMap = parseServerSessionFastModes(payload?.sessionFastModes);
|
|
190
|
+
if (parsedMap !== void 0) confirmedSessionFastModes = parsedMap;
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
defaultFastMode,
|
|
194
|
+
sessionFastModes: deriveSessionFastModes()
|
|
195
|
+
};
|
|
196
|
+
}
|
|
155
197
|
async function refresh(force = false) {
|
|
156
198
|
if (!force && state.quota !== null && nowFn() - state.quota.fetchedAt < ttlMs) return;
|
|
157
199
|
if (inflight !== null && (!force || inflightForce)) return inflight;
|
|
158
200
|
const seq = ++requestSeq;
|
|
159
|
-
const
|
|
201
|
+
const canReplaceFastAtStart = pendingBySession.size === 0;
|
|
202
|
+
const fastEpochAtStart = fastMutationEpoch;
|
|
160
203
|
inflightForce = force;
|
|
161
204
|
setState({
|
|
162
205
|
...state,
|
|
@@ -181,10 +224,10 @@ window.__ModuleLoader__.load({
|
|
|
181
224
|
}
|
|
182
225
|
const payload = await response.json();
|
|
183
226
|
if (seq !== requestSeq) return;
|
|
184
|
-
const
|
|
227
|
+
const fastState = mergeFastStateFromPayload(payload, canReplaceFastAtStart && pendingBySession.size === 0 && fastEpochAtStart === fastMutationEpoch);
|
|
185
228
|
if (payload && payload.ok === true && payload.quota) setState({
|
|
186
229
|
quota: payload.quota,
|
|
187
|
-
|
|
230
|
+
...fastState,
|
|
188
231
|
loading: false,
|
|
189
232
|
error: null
|
|
190
233
|
});
|
|
@@ -193,7 +236,7 @@ window.__ModuleLoader__.load({
|
|
|
193
236
|
const errMsg = payload && !payload.ok && typeof payload.error === "string" ? payload.error : "Invalid response";
|
|
194
237
|
setState({
|
|
195
238
|
quota: fallbackQuota ?? null,
|
|
196
|
-
|
|
239
|
+
...fastState,
|
|
197
240
|
loading: false,
|
|
198
241
|
error: errMsg
|
|
199
242
|
});
|
|
@@ -215,12 +258,30 @@ window.__ModuleLoader__.load({
|
|
|
215
258
|
inflight = task;
|
|
216
259
|
return task;
|
|
217
260
|
}
|
|
218
|
-
async function setFastMode(enabled) {
|
|
261
|
+
async function setFastMode(sessionId, enabled) {
|
|
219
262
|
const nextFastMode = Boolean(enabled);
|
|
263
|
+
const key = normalizeCodexSessionId(sessionId) ?? DEFAULT_SESSION_KEY;
|
|
220
264
|
const seq = ++fastModeSeq;
|
|
265
|
+
fastMutationEpoch += 1;
|
|
266
|
+
latestSeqBySession.set(key, seq);
|
|
267
|
+
pendingBySession.set(key, {
|
|
268
|
+
seq,
|
|
269
|
+
enabled: nextFastMode
|
|
270
|
+
});
|
|
271
|
+
const rollback = (errorMsg) => {
|
|
272
|
+
if (latestSeqBySession.get(key) !== seq) return;
|
|
273
|
+
fastMutationEpoch += 1;
|
|
274
|
+
pendingBySession.delete(key);
|
|
275
|
+
setState({
|
|
276
|
+
...state,
|
|
277
|
+
sessionFastModes: deriveSessionFastModes(),
|
|
278
|
+
error: errorMsg
|
|
279
|
+
});
|
|
280
|
+
};
|
|
221
281
|
setState({
|
|
222
282
|
...state,
|
|
223
|
-
|
|
283
|
+
sessionFastModes: deriveSessionFastModes(),
|
|
284
|
+
error: null
|
|
224
285
|
});
|
|
225
286
|
try {
|
|
226
287
|
const response = await fetchFn(USAGE_ROUTE_PATH, {
|
|
@@ -229,15 +290,43 @@ window.__ModuleLoader__.load({
|
|
|
229
290
|
"Content-Type": "application/json",
|
|
230
291
|
Accept: "application/json"
|
|
231
292
|
},
|
|
232
|
-
body: JSON.stringify({
|
|
293
|
+
body: JSON.stringify({
|
|
294
|
+
sessionId: key,
|
|
295
|
+
fastMode: nextFastMode
|
|
296
|
+
})
|
|
233
297
|
});
|
|
234
|
-
if (
|
|
298
|
+
if (latestSeqBySession.get(key) !== seq) return;
|
|
299
|
+
if (!response.ok) {
|
|
300
|
+
const errPayload = await response.json().catch(() => null);
|
|
301
|
+
rollback(typeof errPayload?.error === "string" && errPayload.error !== "" ? errPayload.error : `HTTP ${response.status}`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
235
304
|
const payload = await response.json();
|
|
236
|
-
if (
|
|
305
|
+
if (latestSeqBySession.get(key) !== seq) return;
|
|
306
|
+
if (!payload || payload.ok === false) {
|
|
307
|
+
rollback(typeof payload?.error === "string" && payload.error !== "" ? payload.error : "Invalid response");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
fastMutationEpoch += 1;
|
|
311
|
+
pendingBySession.delete(key);
|
|
312
|
+
const parsedMap = parseServerSessionFastModes(payload.sessionFastModes);
|
|
313
|
+
if (seq > lastConfirmedFastSeq && parsedMap !== void 0) {
|
|
314
|
+
lastConfirmedFastSeq = seq;
|
|
315
|
+
confirmedSessionFastModes = parsedMap;
|
|
316
|
+
} else confirmedSessionFastModes = {
|
|
317
|
+
...confirmedSessionFastModes,
|
|
318
|
+
[key]: parsedMap?.[key] ?? nextFastMode
|
|
319
|
+
};
|
|
320
|
+
const defaultFastMode = typeof payload.defaultFastMode === "boolean" ? payload.defaultFastMode : state.defaultFastMode;
|
|
321
|
+
setState({
|
|
237
322
|
...state,
|
|
238
|
-
|
|
323
|
+
defaultFastMode,
|
|
324
|
+
sessionFastModes: deriveSessionFastModes(),
|
|
325
|
+
error: null
|
|
239
326
|
});
|
|
240
|
-
} catch {
|
|
327
|
+
} catch (error) {
|
|
328
|
+
rollback(safeErrorMessage(error));
|
|
329
|
+
}
|
|
241
330
|
}
|
|
242
331
|
return {
|
|
243
332
|
getSnapshot: () => state,
|
|
@@ -323,6 +412,18 @@ window.__ModuleLoader__.load({
|
|
|
323
412
|
getSnapshot: () => null,
|
|
324
413
|
subscribe: () => () => {}
|
|
325
414
|
};
|
|
415
|
+
function renderFastBolt(size) {
|
|
416
|
+
return h("svg", {
|
|
417
|
+
className: "dsh-codex-quota-fast-bolt",
|
|
418
|
+
viewBox: "0 0 12 12",
|
|
419
|
+
width: size,
|
|
420
|
+
height: size,
|
|
421
|
+
"aria-hidden": true
|
|
422
|
+
}, h("path", {
|
|
423
|
+
d: "M6.8 1L2.5 6.8h3.1L4.8 11l4.7-5.8H6.4L6.8 1z",
|
|
424
|
+
fill: "currentColor"
|
|
425
|
+
}));
|
|
426
|
+
}
|
|
326
427
|
function CodexQuotaPill(props) {
|
|
327
428
|
const { sessionId, quotaStore, getModelStore, useProjection, useSession, t, getLocale } = props;
|
|
328
429
|
const modelStore = getModelStore(sessionId);
|
|
@@ -388,6 +489,7 @@ window.__ModuleLoader__.load({
|
|
|
388
489
|
})), h("div", { className: "dsh-codex-quota-meta" }, h("span", null, `${t("quota.remainPrefix")}${remainPercent}%`), countdown !== "" ? h("span", null, countdown) : null));
|
|
389
490
|
};
|
|
390
491
|
const quota = quotaSnap.quota;
|
|
492
|
+
const isFastMode = resolveSessionFastMode(quotaSnap, sessionId);
|
|
391
493
|
const secondaryReached = Boolean(quota?.secondary && quota.secondary.usedPercent >= 100);
|
|
392
494
|
const primaryReached = Boolean(quota?.limitReached && !secondaryReached);
|
|
393
495
|
return h("span", {
|
|
@@ -398,7 +500,7 @@ window.__ModuleLoader__.load({
|
|
|
398
500
|
type: "button",
|
|
399
501
|
className: "dsh-codex-quota-trigger",
|
|
400
502
|
"data-level": level,
|
|
401
|
-
"data-fast":
|
|
503
|
+
"data-fast": isFastMode ? "true" : void 0,
|
|
402
504
|
"aria-label": `${t("quota.title")}: ${pillText}`,
|
|
403
505
|
"aria-haspopup": "dialog",
|
|
404
506
|
"aria-expanded": open,
|
|
@@ -425,16 +527,7 @@ window.__ModuleLoader__.load({
|
|
|
425
527
|
r: 5.5,
|
|
426
528
|
strokeDasharray: ringDash,
|
|
427
529
|
transform: "rotate(-90 7 7)"
|
|
428
|
-
})),
|
|
429
|
-
className: "dsh-codex-quota-fast-bolt",
|
|
430
|
-
viewBox: "0 0 12 12",
|
|
431
|
-
width: 11,
|
|
432
|
-
height: 11,
|
|
433
|
-
"aria-hidden": true
|
|
434
|
-
}, h("path", {
|
|
435
|
-
d: "M6.8 1L2.5 6.8h3.1L4.8 11l4.7-5.8H6.4L6.8 1z",
|
|
436
|
-
fill: "currentColor"
|
|
437
|
-
})) : null, h("span", { className: "dsh-codex-quota-text" }, pillText)), open ? h("div", {
|
|
530
|
+
})), isFastMode ? renderFastBolt(11) : null, h("span", { className: "dsh-codex-quota-text" }, pillText)), open ? h("div", {
|
|
438
531
|
className: "dsh-codex-quota-popover",
|
|
439
532
|
role: "dialog",
|
|
440
533
|
"aria-label": t("quota.title"),
|
|
@@ -448,24 +541,15 @@ window.__ModuleLoader__.load({
|
|
|
448
541
|
onClick: () => {
|
|
449
542
|
quotaStore.refresh(true);
|
|
450
543
|
}
|
|
451
|
-
}, 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("
|
|
452
|
-
className: "dsh-codex-quota-fast-bolt",
|
|
453
|
-
viewBox: "0 0 12 12",
|
|
454
|
-
width: 12,
|
|
455
|
-
height: 12,
|
|
456
|
-
"aria-hidden": true
|
|
457
|
-
}, h("path", {
|
|
458
|
-
d: "M6.8 1L2.5 6.8h3.1L4.8 11l4.7-5.8H6.4L6.8 1z",
|
|
459
|
-
fill: "currentColor"
|
|
460
|
-
})), h("span", null, t("quota.fastTitle"))), h("div", { className: "dsh-codex-fast-desc" }, t("quota.fastDesc"))), h("button", {
|
|
544
|
+
}, 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", {
|
|
461
545
|
type: "button",
|
|
462
546
|
role: "switch",
|
|
463
547
|
className: "dsh-codex-fast-switch",
|
|
464
|
-
"aria-checked":
|
|
548
|
+
"aria-checked": isFastMode,
|
|
465
549
|
"aria-label": t("quota.fastTitle"),
|
|
466
|
-
"data-checked":
|
|
550
|
+
"data-checked": isFastMode ? "true" : "false",
|
|
467
551
|
onClick: () => {
|
|
468
|
-
quotaStore.setFastMode(!
|
|
552
|
+
quotaStore.setFastMode(sessionId, !isFastMode);
|
|
469
553
|
}
|
|
470
554
|
}, h("span", { className: "dsh-codex-fast-switch-thumb" }))), quotaSnap.error !== null ? h("div", { className: "dsh-codex-quota-error" }, quotaSnap.error) : null) : null);
|
|
471
555
|
}
|
|
@@ -518,6 +602,7 @@ window.__ModuleLoader__.load({
|
|
|
518
602
|
PROVIDER_ID: PROVIDER,
|
|
519
603
|
USAGE_API_PATH: USAGE_ROUTE_PATH,
|
|
520
604
|
resolveActiveProvider,
|
|
605
|
+
resolveSessionFastMode,
|
|
521
606
|
isCodexProvider,
|
|
522
607
|
didTurnFinish,
|
|
523
608
|
windowLevelOf,
|
package/lib/index.d.ts
CHANGED
|
@@ -93,6 +93,7 @@ interface RawModelsCache {
|
|
|
93
93
|
models?: RawCodexModelEntry[];
|
|
94
94
|
}
|
|
95
95
|
declare function parseRawModelsCache(rawText: string): RawModelsCache;
|
|
96
|
+
declare function normalizeCodexSessionId(sessionId: string | undefined): string | undefined;
|
|
96
97
|
declare function compareClientVersions(a: string | undefined, b: string | undefined): number;
|
|
97
98
|
interface CodexModelMetadata {
|
|
98
99
|
readonly contextWindow: number;
|
|
@@ -111,7 +112,6 @@ declare function parseModelsCache(rawCache: RawModelsCache | null | undefined, b
|
|
|
111
112
|
//#endregion
|
|
112
113
|
//#region src/request.d.ts
|
|
113
114
|
declare function buildCodexUserAgent(version?: string): string;
|
|
114
|
-
declare function normalizeCodexSessionId(sessionId: string | undefined): string | undefined;
|
|
115
115
|
declare function buildCodexHeaders(existingHeaders?: Record<string, string> | undefined, clientVersion?: string): Record<string, string>;
|
|
116
116
|
declare function sanitizeCodexPayload(payload: Record<string, unknown> | null | undefined, modelMeta?: CodexModelMetadata | undefined, fastMode?: boolean): Record<string, unknown> | null | undefined;
|
|
117
117
|
//#endregion
|
|
@@ -132,12 +132,14 @@ interface CodexQuotaSnapshot {
|
|
|
132
132
|
type CodexQuotaResponse = {
|
|
133
133
|
readonly ok: true;
|
|
134
134
|
readonly quota: CodexQuotaSnapshot;
|
|
135
|
-
readonly
|
|
135
|
+
readonly defaultFastMode?: boolean | undefined;
|
|
136
|
+
readonly sessionFastModes?: Readonly<Record<string, boolean>> | undefined;
|
|
136
137
|
} | {
|
|
137
138
|
readonly ok: false;
|
|
138
139
|
readonly error: string;
|
|
139
140
|
readonly quota?: CodexQuotaSnapshot | undefined;
|
|
140
|
-
readonly
|
|
141
|
+
readonly defaultFastMode?: boolean | undefined;
|
|
142
|
+
readonly sessionFastModes?: Readonly<Record<string, boolean>> | undefined;
|
|
141
143
|
};
|
|
142
144
|
interface UsageFetcherOptions {
|
|
143
145
|
readonly resolveCredentials: () => Promise<CodexResolvedCredentials>;
|
package/lib/index.js
CHANGED
|
@@ -35,8 +35,15 @@ const DEFAULT_DISABLED_MODELS = Object.freeze([
|
|
|
35
35
|
function parseRawModelsCache(rawText) {
|
|
36
36
|
const parsed = JSON.parse(rawText);
|
|
37
37
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new SyntaxError("models_cache.json root must be a JSON object");
|
|
38
|
+
const models = parsed.models;
|
|
39
|
+
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
40
|
return parsed;
|
|
39
41
|
}
|
|
42
|
+
function normalizeCodexSessionId(sessionId) {
|
|
43
|
+
if (typeof sessionId !== "string") return void 0;
|
|
44
|
+
const stripped = sessionId.trim().replace(/^session-/iu, "").trim();
|
|
45
|
+
return stripped.length > 0 ? stripped : void 0;
|
|
46
|
+
}
|
|
40
47
|
function isSameModelsCatalogPayload(a, b) {
|
|
41
48
|
if (!a || !b) return false;
|
|
42
49
|
const { fetched_at: _fa, ...restA } = a;
|
|
@@ -89,7 +96,7 @@ function resolveFastServiceTier(entry) {
|
|
|
89
96
|
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;
|
|
90
97
|
return;
|
|
91
98
|
}
|
|
92
|
-
if (Array.isArray(entry.additional_speed_tiers)
|
|
99
|
+
if (Array.isArray(entry.additional_speed_tiers)) return entry.additional_speed_tiers.some((t) => typeof t === "string" && t.trim().toLowerCase() === "fast") ? "priority" : void 0;
|
|
93
100
|
return "priority";
|
|
94
101
|
}
|
|
95
102
|
function parseModelsCache(rawCache, baseModels, disabledModels = [], contextWindows = {}) {
|
|
@@ -194,11 +201,6 @@ function buildCodexUserAgent(version = DEFAULT_CLIENT_VERSION) {
|
|
|
194
201
|
const term = platform() === "darwin" ? "Apple_Terminal/455" : platform() === "win32" ? "WindowsTerminal" : "xterm-256color";
|
|
195
202
|
return `codex_cli_rs/${version} (${detectOsLabel()}; ${cpu}) ${term}`;
|
|
196
203
|
}
|
|
197
|
-
function normalizeCodexSessionId(sessionId) {
|
|
198
|
-
if (typeof sessionId !== "string") return void 0;
|
|
199
|
-
const stripped = sessionId.trim().replace(/^session-/iu, "").trim();
|
|
200
|
-
return stripped.length > 0 ? stripped : void 0;
|
|
201
|
-
}
|
|
202
204
|
function buildCodexHeaders(existingHeaders, clientVersion = DEFAULT_CLIENT_VERSION) {
|
|
203
205
|
const headers = {};
|
|
204
206
|
for (const [key, value] of Object.entries(existingHeaders ?? {})) {
|
|
@@ -218,7 +220,7 @@ function sanitizeCodexPayload(payload, modelMeta, fastMode = false) {
|
|
|
218
220
|
const tier = modelMeta === void 0 ? "priority" : modelMeta.fastServiceTier;
|
|
219
221
|
if (typeof tier === "string" && tier !== "") cleaned.service_tier = tier;
|
|
220
222
|
else delete cleaned.service_tier;
|
|
221
|
-
} else
|
|
223
|
+
} else delete cleaned.service_tier;
|
|
222
224
|
const currentText = cleaned.text && typeof cleaned.text === "object" && !Array.isArray(cleaned.text) ? { ...cleaned.text } : void 0;
|
|
223
225
|
if (modelMeta?.supportVerbosity === false) {
|
|
224
226
|
if (currentText !== void 0) {
|
|
@@ -586,9 +588,17 @@ const MAX_REQUEST_IMAGE_BYTES = 20971520;
|
|
|
586
588
|
const REQUEST_IMAGE_PIXEL_BUDGET = 4194304;
|
|
587
589
|
const REQUEST_IMAGE_MAX_BYTES = 1048576;
|
|
588
590
|
const MAX_LOG_FILE_BYTES = 524288;
|
|
591
|
+
const MAX_PERSISTED_SESSIONS = 200;
|
|
592
|
+
const DEFAULT_SESSION_KEY = "__default__";
|
|
589
593
|
function resolvePluginCacheDir(codexHome) {
|
|
590
594
|
return join(codexHome, PLUGIN_CACHE_DIR_NAME);
|
|
591
595
|
}
|
|
596
|
+
function sliceUtf8Prefix(buf, maxBytes) {
|
|
597
|
+
if (buf.length <= maxBytes) return buf;
|
|
598
|
+
let end = maxBytes;
|
|
599
|
+
while (end > 0 && (buf[end] & 192) === 128) end -= 1;
|
|
600
|
+
return buf.subarray(0, end);
|
|
601
|
+
}
|
|
592
602
|
function appendPluginLog(codexHome, ctx, level, event, detail) {
|
|
593
603
|
const line = `[${new Date().toISOString()}] [${level}] ${event} ${JSON.stringify(detail)}\n`;
|
|
594
604
|
if (level === "ERROR") {
|
|
@@ -596,6 +606,7 @@ function appendPluginLog(codexHome, ctx, level, event, detail) {
|
|
|
596
606
|
else console.error(`[dsh-codex-subscription] ${event}: ${JSON.stringify(detail)}`);
|
|
597
607
|
} else if (level === "WARN") {
|
|
598
608
|
if (typeof ctx.logger?.warn === "function") ctx.logger.warn(`[dsh-codex-subscription] ${event}: ${JSON.stringify(detail)}`);
|
|
609
|
+
else console.warn(`[dsh-codex-subscription] ${event}: ${JSON.stringify(detail)}`);
|
|
599
610
|
}
|
|
600
611
|
try {
|
|
601
612
|
const cacheDir = resolvePluginCacheDir(codexHome);
|
|
@@ -604,18 +615,24 @@ function appendPluginLog(codexHome, ctx, level, event, detail) {
|
|
|
604
615
|
mode: 448
|
|
605
616
|
});
|
|
606
617
|
const logPath = join(cacheDir, LOG_FILE_NAME);
|
|
618
|
+
const rawLineBuf = Buffer.from(line, "utf8");
|
|
619
|
+
const lineBuf = rawLineBuf.length <= MAX_LOG_FILE_BYTES ? rawLineBuf : Buffer.concat([sliceUtf8Prefix(rawLineBuf, 524287), Buffer.from("\n", "utf8")]);
|
|
620
|
+
let st;
|
|
607
621
|
try {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
622
|
+
st = statSync(logPath);
|
|
623
|
+
} catch {
|
|
624
|
+
st = void 0;
|
|
625
|
+
}
|
|
626
|
+
if (st !== void 0 && st.size + lineBuf.length > MAX_LOG_FILE_BYTES) {
|
|
627
|
+
const buf = readFileSync(logPath);
|
|
628
|
+
const keepBudget = Math.max(0, Math.floor(MAX_LOG_FILE_BYTES / 2) - lineBuf.length);
|
|
629
|
+
const startOffset = Math.max(0, buf.length - keepBudget);
|
|
630
|
+
const newlineIdx = keepBudget > 0 ? buf.indexOf(10, startOffset) : -1;
|
|
631
|
+
const tail = newlineIdx >= 0 && newlineIdx + 1 < buf.length ? buf.subarray(newlineIdx + 1) : Buffer.alloc(0);
|
|
632
|
+
writeFileAtomicSync(logPath, Buffer.concat([tail, lineBuf]));
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
appendFileSync(logPath, lineBuf, { mode: 384 });
|
|
619
636
|
} catch {}
|
|
620
637
|
}
|
|
621
638
|
function readBackupCache(codexHome) {
|
|
@@ -625,26 +642,32 @@ function readBackupCache(codexHome) {
|
|
|
625
642
|
return;
|
|
626
643
|
}
|
|
627
644
|
}
|
|
628
|
-
function writeBackupCache(codexHome, raw) {
|
|
645
|
+
function writeBackupCache(codexHome, ctx, raw) {
|
|
629
646
|
try {
|
|
630
647
|
writeFileAtomicSync(join(resolvePluginCacheDir(codexHome), BACKUP_CACHE_FILE_NAME), `${JSON.stringify(raw, null, 2)}\n`);
|
|
631
|
-
} catch {
|
|
632
|
-
}
|
|
633
|
-
function readPersistedFastMode(codexHome) {
|
|
634
|
-
try {
|
|
635
|
-
const raw = JSON.parse(readFileSync(join(resolvePluginCacheDir(codexHome), RUNTIME_STATE_FILE_NAME), "utf8"));
|
|
636
|
-
return typeof raw?.fastMode === "boolean" ? raw.fastMode : void 0;
|
|
637
|
-
} catch {
|
|
638
|
-
return;
|
|
648
|
+
} catch (error) {
|
|
649
|
+
appendPluginLog(codexHome, ctx, "WARN", "catalog.backup_write_error", { error: safeErrorMessage(error) });
|
|
639
650
|
}
|
|
640
651
|
}
|
|
641
|
-
function
|
|
652
|
+
function readPersistedSessionFastModes(codexHome) {
|
|
653
|
+
const result = new Map();
|
|
642
654
|
try {
|
|
643
|
-
|
|
655
|
+
const entries = JSON.parse(readFileSync(join(resolvePluginCacheDir(codexHome), RUNTIME_STATE_FILE_NAME), "utf8"))?.sessionFastModes;
|
|
656
|
+
if (entries && typeof entries === "object" && !Array.isArray(entries)) {
|
|
657
|
+
for (const [k, v] of Object.entries(entries)) if (typeof k === "string" && k !== "" && typeof v === "boolean") result.set(k, v);
|
|
658
|
+
}
|
|
644
659
|
} catch {}
|
|
660
|
+
return result;
|
|
661
|
+
}
|
|
662
|
+
function writePersistedSessionFastModes(codexHome, ctx, sessionFastModes) {
|
|
663
|
+
try {
|
|
664
|
+
writeFileAtomicSync(join(resolvePluginCacheDir(codexHome), RUNTIME_STATE_FILE_NAME), `${JSON.stringify({ sessionFastModes: Object.fromEntries(sessionFastModes) }, null, 2)}\n`);
|
|
665
|
+
} catch (error) {
|
|
666
|
+
appendPluginLog(codexHome, ctx, "WARN", "fast_mode.persist_error", { error: safeErrorMessage(error) });
|
|
667
|
+
}
|
|
645
668
|
}
|
|
646
669
|
const Config = z.object({
|
|
647
|
-
fastMode: z.boolean().default(false).description("
|
|
670
|
+
fastMode: z.boolean().default(false).description("新会话默认是否开启 Fast 极速模式(未填时默认 false;单会话可在右下角限额弹窗内独立切换)").volatile(),
|
|
648
671
|
disabledModels: z.array(z.string()).default([...DEFAULT_DISABLED_MODELS]).description("在模型选择器中屏蔽的模型 slug 列表").volatile(),
|
|
649
672
|
contextWindows: z.dict(z.number()).default({}).description("按模型 slug 覆盖上下文窗口大小(tokens),未配置时默认使用 872000").volatile()
|
|
650
673
|
});
|
|
@@ -682,19 +705,30 @@ function apply(ctx, config = {}) {
|
|
|
682
705
|
const baseModels = baseProvider.getModels();
|
|
683
706
|
const retryPolicy = resolveRetryPolicy(void 0, "codex-subscription retryPolicy");
|
|
684
707
|
const getCodexHome = () => resolveCodexHome();
|
|
685
|
-
const getConfigFastMode = () =>
|
|
708
|
+
const getConfigFastMode = () => readVolatile(config.fastMode, false) === true;
|
|
686
709
|
const getDisabledModels = () => readVolatile(config.disabledModels, DEFAULT_DISABLED_MODELS);
|
|
687
710
|
const getContextWindows = () => readVolatile(config.contextWindows, {});
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
711
|
+
const sessionFastModes = readPersistedSessionFastModes(getCodexHome());
|
|
712
|
+
const getEffectiveFastMode = (sessionId) => {
|
|
713
|
+
const key = normalizeCodexSessionId(sessionId);
|
|
714
|
+
if (key !== void 0 && sessionFastModes.has(key)) return sessionFastModes.get(key) === true;
|
|
715
|
+
if (key === void 0 && sessionFastModes.has(DEFAULT_SESSION_KEY)) return sessionFastModes.get(DEFAULT_SESSION_KEY) === true;
|
|
716
|
+
return getConfigFastMode();
|
|
717
|
+
};
|
|
718
|
+
const setSessionFastMode = (sessionId, nextFastMode) => {
|
|
692
719
|
const codexHome = getCodexHome();
|
|
693
|
-
|
|
694
|
-
|
|
720
|
+
const key = normalizeCodexSessionId(sessionId) ?? DEFAULT_SESSION_KEY;
|
|
721
|
+
sessionFastModes.delete(key);
|
|
722
|
+
sessionFastModes.set(key, nextFastMode);
|
|
723
|
+
while (sessionFastModes.size > MAX_PERSISTED_SESSIONS) {
|
|
724
|
+
const oldest = sessionFastModes.keys().next().value;
|
|
725
|
+
if (oldest === void 0) break;
|
|
726
|
+
sessionFastModes.delete(oldest);
|
|
727
|
+
}
|
|
728
|
+
writePersistedSessionFastModes(codexHome, ctx, sessionFastModes);
|
|
695
729
|
appendPluginLog(codexHome, ctx, "INFO", "fast_mode.updated", {
|
|
696
|
-
|
|
697
|
-
|
|
730
|
+
sessionId: key,
|
|
731
|
+
fastMode: nextFastMode
|
|
698
732
|
});
|
|
699
733
|
};
|
|
700
734
|
const authManager = createAuthManager({
|
|
@@ -704,6 +738,7 @@ function apply(ctx, config = {}) {
|
|
|
704
738
|
});
|
|
705
739
|
let fileSignature;
|
|
706
740
|
let cachedRaw;
|
|
741
|
+
let lastRejectedRaw;
|
|
707
742
|
let rawRevision = 0;
|
|
708
743
|
let snapshotSignature;
|
|
709
744
|
let currentCatalog = {
|
|
@@ -734,7 +769,7 @@ function apply(ctx, config = {}) {
|
|
|
734
769
|
transport: "sse",
|
|
735
770
|
headers: buildCodexHeaders(rest.headers, catalog.clientVersion),
|
|
736
771
|
async onPayload(payload, requestModel) {
|
|
737
|
-
return sanitizeCodexPayload(await onPayload?.(payload, requestModel) ?? payload, modelMeta, getEffectiveFastMode());
|
|
772
|
+
return sanitizeCodexPayload(await onPayload?.(payload, requestModel) ?? payload, modelMeta, getEffectiveFastMode(normalizedSessionId));
|
|
738
773
|
}
|
|
739
774
|
};
|
|
740
775
|
}
|
|
@@ -767,6 +802,7 @@ function apply(ctx, config = {}) {
|
|
|
767
802
|
const contextWindows = getContextWindows();
|
|
768
803
|
const setActiveRaw = (next) => {
|
|
769
804
|
cachedRaw = next;
|
|
805
|
+
lastRejectedRaw = void 0;
|
|
770
806
|
rawRevision += 1;
|
|
771
807
|
};
|
|
772
808
|
const tryRestoreFromBackup = (extra, minVersion) => {
|
|
@@ -805,16 +841,20 @@ function apply(ctx, config = {}) {
|
|
|
805
841
|
fileSignature = nextFileSig;
|
|
806
842
|
try {
|
|
807
843
|
const incomingRaw = parseRawModelsCache(readFileSync(cachePath, "utf8"));
|
|
808
|
-
if (cachedRaw !== void 0 && compareClientVersions(incomingRaw.client_version, cachedRaw.client_version) < 0)
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
844
|
+
if (cachedRaw !== void 0 && compareClientVersions(incomingRaw.client_version, cachedRaw.client_version) < 0) {
|
|
845
|
+
if (!isSameModelsCatalogPayload(lastRejectedRaw, incomingRaw)) {
|
|
846
|
+
lastRejectedRaw = incomingRaw;
|
|
847
|
+
appendPluginLog(codexHome, ctx, "WARN", "catalog.downgrade_rejected", {
|
|
848
|
+
cachePath,
|
|
849
|
+
keptVersion: cachedRaw.client_version ?? "0.163.0",
|
|
850
|
+
rejectedVersion: incomingRaw.client_version ?? "unknown",
|
|
851
|
+
incomingFetchedAt: incomingRaw.fetched_at ?? null
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
} else if (cachedRaw !== void 0 || !tryRestoreFromBackup({ diskVersion: incomingRaw.client_version ?? "unknown" }, incomingRaw.client_version)) {
|
|
815
855
|
if (!isSameModelsCatalogPayload(cachedRaw, incomingRaw)) {
|
|
816
856
|
setActiveRaw(incomingRaw);
|
|
817
|
-
writeBackupCache(codexHome, incomingRaw);
|
|
857
|
+
writeBackupCache(codexHome, ctx, incomingRaw);
|
|
818
858
|
appendPluginLog(codexHome, ctx, "INFO", "catalog.load", {
|
|
819
859
|
cachePath,
|
|
820
860
|
clientVersion: incomingRaw.client_version ?? "0.163.0",
|
|
@@ -902,11 +942,25 @@ function apply(ctx, config = {}) {
|
|
|
902
942
|
requestBody: "buffered",
|
|
903
943
|
fetch: async (request) => {
|
|
904
944
|
if (request.method === "POST") {
|
|
905
|
-
|
|
906
|
-
|
|
945
|
+
let body;
|
|
946
|
+
try {
|
|
947
|
+
body = await request.json();
|
|
948
|
+
} catch {
|
|
949
|
+
body = null;
|
|
950
|
+
}
|
|
951
|
+
const record = body !== null && typeof body === "object" && !Array.isArray(body) ? body : null;
|
|
952
|
+
if (record === null || typeof record.fastMode !== "boolean" || record.sessionId !== void 0 && typeof record.sessionId !== "string") return new Response(JSON.stringify({
|
|
953
|
+
ok: false,
|
|
954
|
+
error: "Invalid request body: boolean fastMode is required"
|
|
955
|
+
}), {
|
|
956
|
+
status: 400,
|
|
957
|
+
headers: { "content-type": "application/json" }
|
|
958
|
+
});
|
|
959
|
+
setSessionFastMode(record.sessionId, record.fastMode);
|
|
907
960
|
return new Response(JSON.stringify({
|
|
908
961
|
ok: true,
|
|
909
|
-
|
|
962
|
+
defaultFastMode: getConfigFastMode(),
|
|
963
|
+
sessionFastModes: Object.fromEntries(sessionFastModes)
|
|
910
964
|
}), {
|
|
911
965
|
status: 200,
|
|
912
966
|
headers: { "content-type": "application/json" }
|
|
@@ -916,7 +970,8 @@ function apply(ctx, config = {}) {
|
|
|
916
970
|
const result = await usageFetcher.getUsage(force);
|
|
917
971
|
return new Response(JSON.stringify({
|
|
918
972
|
...result,
|
|
919
|
-
|
|
973
|
+
defaultFastMode: getConfigFastMode(),
|
|
974
|
+
sessionFastModes: Object.fromEntries(sessionFastModes)
|
|
920
975
|
}), {
|
|
921
976
|
status: 200,
|
|
922
977
|
headers: { "content-type": "application/json" }
|
|
@@ -929,11 +984,6 @@ function apply(ctx, config = {}) {
|
|
|
929
984
|
if (typeof ctx.inject === "function") ctx.inject(["connection"], mountUsageRoute);
|
|
930
985
|
else mountUsageRoute(ctx);
|
|
931
986
|
ctx.on?.("loader/volatile-update", () => {
|
|
932
|
-
const currentConfigFastMode = getConfigFastMode();
|
|
933
|
-
if (currentConfigFastMode !== lastConfigFastMode) {
|
|
934
|
-
lastConfigFastMode = currentConfigFastMode;
|
|
935
|
-
setFastModeOverride(currentConfigFastMode, "config");
|
|
936
|
-
}
|
|
937
987
|
ensureSnapshot();
|
|
938
988
|
ctx.llm.emitAdaptersUpdated?.();
|
|
939
989
|
});
|