@lyogvce/dsh-codex-subscription 0.2.6 → 0.2.8
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 +2 -1
- package/lib/client.js +118 -7
- package/lib/index.d.ts +15 -2
- package/lib/index.js +87 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
- 通过 `contextWindows` 按模型 `slug` 自定义向 DSH 会话与自动压缩器暴露的预算窗口(例如设为 `400000`);未配置时默认使用模型物理上限 `max_context_window`(`872000`)。同时底层 `PiCodexModel.contextWindow` 始终保持物理上限 `872000`,彻底防止 `dsh-llm-pi-ai` 在预算窗口与物理上限之间误判 `CONTEXT_WINDOW_EXCEEDED`。
|
|
21
21
|
4. **原生多轮思考链回放与工具调用**
|
|
22
22
|
- 底层复用 DSH 内置的 `PiAiAdapter` 与 `@earendil-works/pi-ai` 的 `openaiCodexProvider()`,固定使用 `sse` 传输(含 `zstd` 请求体压缩、规范化纯 UUID `session-id` / `x-client-request-id` 缓存头、`reasoning.encrypted_content` 签名回放与图片自动缩放卸载)。
|
|
23
|
-
5. **Codex
|
|
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 推理速度)**:每个会话可在限额弹窗底部独立开关 Fast 模式(互不影响,新会话默认继承插件设置中的 `fastMode`,未配置时默认 `false` 关闭),状态自动持久化至 `~/.codex/cache/dsh-codex-subscription/runtime-state.json`。开启后当前会话胶囊显示闪电标识 `⚡`,并在该会话请求体中自动注入 `"service_tier": "priority"`。
|
|
25
26
|
|
|
26
27
|
---
|
|
27
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,21 +152,40 @@ window.__ModuleLoader__.load({
|
|
|
139
152
|
const ttlMs = options.ttlMs ?? 6e4;
|
|
140
153
|
let state = {
|
|
141
154
|
quota: null,
|
|
155
|
+
defaultFastMode: false,
|
|
156
|
+
sessionFastModes: {},
|
|
142
157
|
loading: false,
|
|
143
158
|
error: null
|
|
144
159
|
};
|
|
145
160
|
let inflight = null;
|
|
146
161
|
let inflightForce = false;
|
|
147
162
|
let requestSeq = 0;
|
|
163
|
+
let fastModeSeq = 0;
|
|
148
164
|
const listeners = new Set();
|
|
149
165
|
function setState(next) {
|
|
150
166
|
state = next;
|
|
151
167
|
for (const listener of listeners) listener();
|
|
152
168
|
}
|
|
169
|
+
function mergeFastStateFromPayload(payload, allowSessionReplace) {
|
|
170
|
+
const defaultFastMode = typeof payload?.defaultFastMode === "boolean" ? payload.defaultFastMode : state.defaultFastMode;
|
|
171
|
+
if (allowSessionReplace && payload?.sessionFastModes && typeof payload.sessionFastModes === "object" && !Array.isArray(payload.sessionFastModes)) {
|
|
172
|
+
const nextMap = {};
|
|
173
|
+
for (const [k, v] of Object.entries(payload.sessionFastModes)) if (typeof k === "string" && k !== "" && typeof v === "boolean") nextMap[k] = v;
|
|
174
|
+
return {
|
|
175
|
+
defaultFastMode,
|
|
176
|
+
sessionFastModes: nextMap
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
defaultFastMode,
|
|
181
|
+
sessionFastModes: state.sessionFastModes
|
|
182
|
+
};
|
|
183
|
+
}
|
|
153
184
|
async function refresh(force = false) {
|
|
154
185
|
if (!force && state.quota !== null && nowFn() - state.quota.fetchedAt < ttlMs) return;
|
|
155
186
|
if (inflight !== null && (!force || inflightForce)) return inflight;
|
|
156
187
|
const seq = ++requestSeq;
|
|
188
|
+
const fastSeqAtStart = fastModeSeq;
|
|
157
189
|
inflightForce = force;
|
|
158
190
|
setState({
|
|
159
191
|
...state,
|
|
@@ -170,7 +202,7 @@ window.__ModuleLoader__.load({
|
|
|
170
202
|
if (seq !== requestSeq) return;
|
|
171
203
|
if (!response.ok) {
|
|
172
204
|
setState({
|
|
173
|
-
|
|
205
|
+
...state,
|
|
174
206
|
loading: false,
|
|
175
207
|
error: `HTTP ${response.status}`
|
|
176
208
|
});
|
|
@@ -178,8 +210,10 @@ window.__ModuleLoader__.load({
|
|
|
178
210
|
}
|
|
179
211
|
const payload = await response.json();
|
|
180
212
|
if (seq !== requestSeq) return;
|
|
213
|
+
const fastState = mergeFastStateFromPayload(payload, fastSeqAtStart === fastModeSeq);
|
|
181
214
|
if (payload && payload.ok === true && payload.quota) setState({
|
|
182
215
|
quota: payload.quota,
|
|
216
|
+
...fastState,
|
|
183
217
|
loading: false,
|
|
184
218
|
error: null
|
|
185
219
|
});
|
|
@@ -188,6 +222,7 @@ window.__ModuleLoader__.load({
|
|
|
188
222
|
const errMsg = payload && !payload.ok && typeof payload.error === "string" ? payload.error : "Invalid response";
|
|
189
223
|
setState({
|
|
190
224
|
quota: fallbackQuota ?? null,
|
|
225
|
+
...fastState,
|
|
191
226
|
loading: false,
|
|
192
227
|
error: errMsg
|
|
193
228
|
});
|
|
@@ -195,7 +230,7 @@ window.__ModuleLoader__.load({
|
|
|
195
230
|
} catch (error) {
|
|
196
231
|
if (seq !== requestSeq) return;
|
|
197
232
|
setState({
|
|
198
|
-
|
|
233
|
+
...state,
|
|
199
234
|
loading: false,
|
|
200
235
|
error: safeErrorMessage(error)
|
|
201
236
|
});
|
|
@@ -209,6 +244,37 @@ window.__ModuleLoader__.load({
|
|
|
209
244
|
inflight = task;
|
|
210
245
|
return task;
|
|
211
246
|
}
|
|
247
|
+
async function setFastMode(sessionId, enabled) {
|
|
248
|
+
const nextFastMode = Boolean(enabled);
|
|
249
|
+
const key = normalizeCodexSessionId(sessionId) ?? DEFAULT_SESSION_KEY;
|
|
250
|
+
const seq = ++fastModeSeq;
|
|
251
|
+
setState({
|
|
252
|
+
...state,
|
|
253
|
+
sessionFastModes: {
|
|
254
|
+
...state.sessionFastModes,
|
|
255
|
+
[key]: nextFastMode
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
try {
|
|
259
|
+
const response = await fetchFn(USAGE_ROUTE_PATH, {
|
|
260
|
+
method: "POST",
|
|
261
|
+
headers: {
|
|
262
|
+
"Content-Type": "application/json",
|
|
263
|
+
Accept: "application/json"
|
|
264
|
+
},
|
|
265
|
+
body: JSON.stringify({
|
|
266
|
+
sessionId: key,
|
|
267
|
+
fastMode: nextFastMode
|
|
268
|
+
})
|
|
269
|
+
});
|
|
270
|
+
if (seq !== fastModeSeq || !response.ok) return;
|
|
271
|
+
const payload = await response.json();
|
|
272
|
+
if (seq === fastModeSeq) setState({
|
|
273
|
+
...state,
|
|
274
|
+
...mergeFastStateFromPayload(payload, true)
|
|
275
|
+
});
|
|
276
|
+
} catch {}
|
|
277
|
+
}
|
|
212
278
|
return {
|
|
213
279
|
getSnapshot: () => state,
|
|
214
280
|
subscribe(listener) {
|
|
@@ -217,7 +283,8 @@ window.__ModuleLoader__.load({
|
|
|
217
283
|
listeners.delete(listener);
|
|
218
284
|
};
|
|
219
285
|
},
|
|
220
|
-
refresh
|
|
286
|
+
refresh,
|
|
287
|
+
setFastMode
|
|
221
288
|
};
|
|
222
289
|
}
|
|
223
290
|
//#endregion
|
|
@@ -250,6 +317,15 @@ window.__ModuleLoader__.load({
|
|
|
250
317
|
".dsh-codex-quota-bar-fill[data-level=\"warn\"]{background:var(--dsw-alias-state-warn-primary)}",
|
|
251
318
|
".dsh-codex-quota-bar-fill[data-level=\"error\"]{background:var(--dsw-alias-state-error-primary)}",
|
|
252
319
|
".dsh-codex-quota-meta{display:flex;justify-content:space-between;gap:8px;font-size:11px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}",
|
|
320
|
+
".dsh-codex-quota-fast-bolt{color:var(--dsw-alias-state-warn-primary,#f59e0b);flex:none}",
|
|
321
|
+
".dsh-codex-fast-row{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:8px;padding-top:8px;border-top:1px solid var(--dsw-alias-border-l1)}",
|
|
322
|
+
".dsh-codex-fast-info{display:flex;flex-direction:column;gap:1px;min-width:0}",
|
|
323
|
+
".dsh-codex-fast-title{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:500;color:var(--dsw-alias-label-primary)}",
|
|
324
|
+
".dsh-codex-fast-desc{font-size:11px;line-height:15px;color:var(--dsw-alias-label-tertiary)}",
|
|
325
|
+
".dsh-codex-fast-switch{position:relative;width:32px;height:18px;border-radius:999px;border:none;padding:0;background:var(--dsw-alias-border-l2,rgba(128,128,128,0.35));cursor:pointer;flex:none;transition:background-color .15s ease}",
|
|
326
|
+
".dsh-codex-fast-switch[data-checked=\"true\"]{background:var(--dsw-alias-brand-primary,#3b82f6)}",
|
|
327
|
+
".dsh-codex-fast-switch-thumb{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,0.2);transition:transform .15s ease}",
|
|
328
|
+
".dsh-codex-fast-switch[data-checked=\"true\"] .dsh-codex-fast-switch-thumb{transform:translateX(14px)}",
|
|
253
329
|
".dsh-codex-quota-error{margin-top:6px;padding:4px 6px;border-radius:4px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-state-error-primary);font-size:11px;word-break:break-word}"
|
|
254
330
|
].join("");
|
|
255
331
|
//#endregion
|
|
@@ -265,7 +341,9 @@ window.__ModuleLoader__.load({
|
|
|
265
341
|
"quota.refresh": "↻ 刷新",
|
|
266
342
|
"quota.refreshing": "↻ 刷新中…",
|
|
267
343
|
"quota.usedPrefix": "已用 ",
|
|
268
|
-
"quota.remainPrefix": "剩余 "
|
|
344
|
+
"quota.remainPrefix": "剩余 ",
|
|
345
|
+
"quota.fastTitle": "Fast 极速模式",
|
|
346
|
+
"quota.fastDesc": "2x 推理速度,消耗更多限额"
|
|
269
347
|
};
|
|
270
348
|
const en = {
|
|
271
349
|
"quota.title": "Codex Subscription Quota",
|
|
@@ -273,7 +351,9 @@ window.__ModuleLoader__.load({
|
|
|
273
351
|
"quota.refresh": "↻ Refresh",
|
|
274
352
|
"quota.refreshing": "↻ Refreshing…",
|
|
275
353
|
"quota.usedPrefix": "Used ",
|
|
276
|
-
"quota.remainPrefix": "Remaining "
|
|
354
|
+
"quota.remainPrefix": "Remaining ",
|
|
355
|
+
"quota.fastTitle": "Fast Mode",
|
|
356
|
+
"quota.fastDesc": "2x speed, increased usage"
|
|
277
357
|
};
|
|
278
358
|
const EMPTY_MODEL_STORE = {
|
|
279
359
|
getSnapshot: () => null,
|
|
@@ -344,6 +424,7 @@ window.__ModuleLoader__.load({
|
|
|
344
424
|
})), h("div", { className: "dsh-codex-quota-meta" }, h("span", null, `${t("quota.remainPrefix")}${remainPercent}%`), countdown !== "" ? h("span", null, countdown) : null));
|
|
345
425
|
};
|
|
346
426
|
const quota = quotaSnap.quota;
|
|
427
|
+
const isFastMode = resolveSessionFastMode(quotaSnap, sessionId);
|
|
347
428
|
const secondaryReached = Boolean(quota?.secondary && quota.secondary.usedPercent >= 100);
|
|
348
429
|
const primaryReached = Boolean(quota?.limitReached && !secondaryReached);
|
|
349
430
|
return h("span", {
|
|
@@ -354,6 +435,7 @@ window.__ModuleLoader__.load({
|
|
|
354
435
|
type: "button",
|
|
355
436
|
className: "dsh-codex-quota-trigger",
|
|
356
437
|
"data-level": level,
|
|
438
|
+
"data-fast": isFastMode ? "true" : void 0,
|
|
357
439
|
"aria-label": `${t("quota.title")}: ${pillText}`,
|
|
358
440
|
"aria-haspopup": "dialog",
|
|
359
441
|
"aria-expanded": open,
|
|
@@ -380,7 +462,16 @@ window.__ModuleLoader__.load({
|
|
|
380
462
|
r: 5.5,
|
|
381
463
|
strokeDasharray: ringDash,
|
|
382
464
|
transform: "rotate(-90 7 7)"
|
|
383
|
-
})),
|
|
465
|
+
})), isFastMode ? h("svg", {
|
|
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", {
|
|
384
475
|
className: "dsh-codex-quota-popover",
|
|
385
476
|
role: "dialog",
|
|
386
477
|
"aria-label": t("quota.title"),
|
|
@@ -394,7 +485,26 @@ window.__ModuleLoader__.load({
|
|
|
394
485
|
onClick: () => {
|
|
395
486
|
quotaStore.refresh(true);
|
|
396
487
|
}
|
|
397
|
-
}, 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,
|
|
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("svg", {
|
|
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", {
|
|
498
|
+
type: "button",
|
|
499
|
+
role: "switch",
|
|
500
|
+
className: "dsh-codex-fast-switch",
|
|
501
|
+
"aria-checked": isFastMode,
|
|
502
|
+
"aria-label": t("quota.fastTitle"),
|
|
503
|
+
"data-checked": isFastMode ? "true" : "false",
|
|
504
|
+
onClick: () => {
|
|
505
|
+
quotaStore.setFastMode(sessionId, !isFastMode);
|
|
506
|
+
}
|
|
507
|
+
}, h("span", { className: "dsh-codex-fast-switch-thumb" }))), quotaSnap.error !== null ? h("div", { className: "dsh-codex-quota-error" }, quotaSnap.error) : null) : null);
|
|
398
508
|
}
|
|
399
509
|
const inject = ["slots", "locale"];
|
|
400
510
|
function apply(ctx) {
|
|
@@ -445,6 +555,7 @@ window.__ModuleLoader__.load({
|
|
|
445
555
|
PROVIDER_ID: PROVIDER,
|
|
446
556
|
USAGE_API_PATH: USAGE_ROUTE_PATH,
|
|
447
557
|
resolveActiveProvider,
|
|
558
|
+
resolveSessionFastMode,
|
|
448
559
|
isCodexProvider,
|
|
449
560
|
didTurnFinish,
|
|
450
561
|
windowLevelOf,
|
package/lib/index.d.ts
CHANGED
|
@@ -61,10 +61,16 @@ declare function createAuthManager(options: AuthManagerOptions): {
|
|
|
61
61
|
//#region src/catalog.d.ts
|
|
62
62
|
export declare const PLUGIN_CACHE_DIR_NAME = "cache/dsh-codex-subscription";
|
|
63
63
|
export declare const BACKUP_CACHE_FILE_NAME = "models-cache-backup.json";
|
|
64
|
+
export declare const RUNTIME_STATE_FILE_NAME = "runtime-state.json";
|
|
64
65
|
interface RawReasoningLevel {
|
|
65
66
|
effort?: string;
|
|
66
67
|
description?: string;
|
|
67
68
|
}
|
|
69
|
+
interface RawServiceTier {
|
|
70
|
+
id?: string;
|
|
71
|
+
name?: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
}
|
|
68
74
|
interface RawCodexModelEntry {
|
|
69
75
|
slug?: string;
|
|
70
76
|
display_name?: string;
|
|
@@ -78,6 +84,8 @@ interface RawCodexModelEntry {
|
|
|
78
84
|
default_reasoning_level?: string;
|
|
79
85
|
support_verbosity?: boolean;
|
|
80
86
|
default_verbosity?: string;
|
|
87
|
+
additional_speed_tiers?: unknown[];
|
|
88
|
+
service_tiers?: RawServiceTier[];
|
|
81
89
|
}
|
|
82
90
|
interface RawModelsCache {
|
|
83
91
|
fetched_at?: string;
|
|
@@ -85,12 +93,14 @@ interface RawModelsCache {
|
|
|
85
93
|
models?: RawCodexModelEntry[];
|
|
86
94
|
}
|
|
87
95
|
declare function parseRawModelsCache(rawText: string): RawModelsCache;
|
|
96
|
+
declare function normalizeCodexSessionId(sessionId: string | undefined): string | undefined;
|
|
88
97
|
declare function compareClientVersions(a: string | undefined, b: string | undefined): number;
|
|
89
98
|
interface CodexModelMetadata {
|
|
90
99
|
readonly contextWindow: number;
|
|
91
100
|
readonly supportVerbosity: boolean;
|
|
92
101
|
readonly defaultVerbosity: 'low' | 'medium' | 'high';
|
|
93
102
|
readonly defaultReasoningLevel?: string | undefined;
|
|
103
|
+
readonly fastServiceTier?: string | undefined;
|
|
94
104
|
}
|
|
95
105
|
interface ParsedModelsCatalog {
|
|
96
106
|
readonly clientVersion: string;
|
|
@@ -102,9 +112,8 @@ declare function parseModelsCache(rawCache: RawModelsCache | null | undefined, b
|
|
|
102
112
|
//#endregion
|
|
103
113
|
//#region src/request.d.ts
|
|
104
114
|
declare function buildCodexUserAgent(version?: string): string;
|
|
105
|
-
declare function normalizeCodexSessionId(sessionId: string | undefined): string | undefined;
|
|
106
115
|
declare function buildCodexHeaders(existingHeaders?: Record<string, string> | undefined, clientVersion?: string): Record<string, string>;
|
|
107
|
-
declare function sanitizeCodexPayload(payload: Record<string, unknown> | null | undefined, modelMeta?: CodexModelMetadata | undefined): Record<string, unknown> | null | undefined;
|
|
116
|
+
declare function sanitizeCodexPayload(payload: Record<string, unknown> | null | undefined, modelMeta?: CodexModelMetadata | undefined, fastMode?: boolean): Record<string, unknown> | null | undefined;
|
|
108
117
|
//#endregion
|
|
109
118
|
//#region src/usage.d.ts
|
|
110
119
|
declare function safeErrorMessage(error: unknown): string;
|
|
@@ -123,10 +132,14 @@ interface CodexQuotaSnapshot {
|
|
|
123
132
|
type CodexQuotaResponse = {
|
|
124
133
|
readonly ok: true;
|
|
125
134
|
readonly quota: CodexQuotaSnapshot;
|
|
135
|
+
readonly defaultFastMode?: boolean | undefined;
|
|
136
|
+
readonly sessionFastModes?: Readonly<Record<string, boolean>> | undefined;
|
|
126
137
|
} | {
|
|
127
138
|
readonly ok: false;
|
|
128
139
|
readonly error: string;
|
|
129
140
|
readonly quota?: CodexQuotaSnapshot | undefined;
|
|
141
|
+
readonly defaultFastMode?: boolean | undefined;
|
|
142
|
+
readonly sessionFastModes?: Readonly<Record<string, boolean>> | undefined;
|
|
130
143
|
};
|
|
131
144
|
interface UsageFetcherOptions {
|
|
132
145
|
readonly resolveCredentials: () => Promise<CodexResolvedCredentials>;
|
package/lib/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const DEFAULT_CLIENT_VERSION = "0.163.0";
|
|
|
15
15
|
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
|
+
const RUNTIME_STATE_FILE_NAME = "runtime-state.json";
|
|
18
19
|
const THINKING_LEVELS = [
|
|
19
20
|
"off",
|
|
20
21
|
"minimal",
|
|
@@ -36,6 +37,11 @@ function parseRawModelsCache(rawText) {
|
|
|
36
37
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new SyntaxError("models_cache.json root must be a JSON object");
|
|
37
38
|
return parsed;
|
|
38
39
|
}
|
|
40
|
+
function normalizeCodexSessionId(sessionId) {
|
|
41
|
+
if (typeof sessionId !== "string") return void 0;
|
|
42
|
+
const stripped = sessionId.trim().replace(/^session-/iu, "").trim();
|
|
43
|
+
return stripped.length > 0 ? stripped : void 0;
|
|
44
|
+
}
|
|
39
45
|
function isSameModelsCatalogPayload(a, b) {
|
|
40
46
|
if (!a || !b) return false;
|
|
41
47
|
const { fetched_at: _fa, ...restA } = a;
|
|
@@ -81,6 +87,16 @@ function isVerbosity(value) {
|
|
|
81
87
|
function isPositiveSafeInt(value) {
|
|
82
88
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
83
89
|
}
|
|
90
|
+
function resolveFastServiceTier(entry) {
|
|
91
|
+
if (Array.isArray(entry.service_tiers)) {
|
|
92
|
+
const matched = entry.service_tiers.find((tier) => tier !== null && typeof tier === "object" && typeof tier.id === "string" && tier.id.trim() !== "" && (tier.id.trim() === "priority" || typeof tier.name === "string" && tier.name.trim().toLowerCase() === "fast"));
|
|
93
|
+
if (matched && typeof matched.id === "string") return matched.id.trim();
|
|
94
|
+
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
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (Array.isArray(entry.additional_speed_tiers) && entry.additional_speed_tiers.some((t) => typeof t === "string" && t.trim().toLowerCase() === "fast")) return "priority";
|
|
98
|
+
return "priority";
|
|
99
|
+
}
|
|
84
100
|
function parseModelsCache(rawCache, baseModels, disabledModels = [], contextWindows = {}) {
|
|
85
101
|
const disabledSet = new Set((Array.isArray(disabledModels) ? disabledModels : []).filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean));
|
|
86
102
|
const clientVersion = typeof rawCache?.client_version === "string" && rawCache.client_version.trim() !== "" ? rawCache.client_version.trim() : DEFAULT_CLIENT_VERSION;
|
|
@@ -108,11 +124,13 @@ function parseModelsCache(rawCache, baseModels, disabledModels = [], contextWind
|
|
|
108
124
|
const supportVerbosity = entry.support_verbosity !== false;
|
|
109
125
|
const defaultVerbosity = isVerbosity(entry.default_verbosity) ? entry.default_verbosity : "low";
|
|
110
126
|
const defaultReasoningLevel = normalizeDefaultReasoningLevel(entry.default_reasoning_level, thinkingLevelMap);
|
|
127
|
+
const fastServiceTier = resolveFastServiceTier(entry);
|
|
111
128
|
metadataById.set(id, {
|
|
112
129
|
contextWindow: targetContextWindow,
|
|
113
130
|
supportVerbosity,
|
|
114
131
|
defaultVerbosity,
|
|
115
|
-
defaultReasoningLevel
|
|
132
|
+
defaultReasoningLevel,
|
|
133
|
+
fastServiceTier
|
|
116
134
|
});
|
|
117
135
|
models.push({
|
|
118
136
|
...base ?? {},
|
|
@@ -181,11 +199,6 @@ function buildCodexUserAgent(version = DEFAULT_CLIENT_VERSION) {
|
|
|
181
199
|
const term = platform() === "darwin" ? "Apple_Terminal/455" : platform() === "win32" ? "WindowsTerminal" : "xterm-256color";
|
|
182
200
|
return `codex_cli_rs/${version} (${detectOsLabel()}; ${cpu}) ${term}`;
|
|
183
201
|
}
|
|
184
|
-
function normalizeCodexSessionId(sessionId) {
|
|
185
|
-
if (typeof sessionId !== "string") return void 0;
|
|
186
|
-
const stripped = sessionId.trim().replace(/^session-/iu, "").trim();
|
|
187
|
-
return stripped.length > 0 ? stripped : void 0;
|
|
188
|
-
}
|
|
189
202
|
function buildCodexHeaders(existingHeaders, clientVersion = DEFAULT_CLIENT_VERSION) {
|
|
190
203
|
const headers = {};
|
|
191
204
|
for (const [key, value] of Object.entries(existingHeaders ?? {})) {
|
|
@@ -197,11 +210,15 @@ function buildCodexHeaders(existingHeaders, clientVersion = DEFAULT_CLIENT_VERSI
|
|
|
197
210
|
headers["User-Agent"] = buildCodexUserAgent(clientVersion);
|
|
198
211
|
return headers;
|
|
199
212
|
}
|
|
200
|
-
function sanitizeCodexPayload(payload, modelMeta) {
|
|
213
|
+
function sanitizeCodexPayload(payload, modelMeta, fastMode = false) {
|
|
201
214
|
if (!payload || typeof payload !== "object") return payload;
|
|
202
215
|
const cleaned = { ...payload };
|
|
203
216
|
for (const field of FORBIDDEN_PAYLOAD_FIELDS) delete cleaned[field];
|
|
204
|
-
if (
|
|
217
|
+
if (fastMode) {
|
|
218
|
+
const tier = modelMeta === void 0 ? "priority" : modelMeta.fastServiceTier;
|
|
219
|
+
if (typeof tier === "string" && tier !== "") cleaned.service_tier = tier;
|
|
220
|
+
else delete cleaned.service_tier;
|
|
221
|
+
} else if (cleaned.service_tier !== "priority" && cleaned.service_tier !== "ultrafast") delete cleaned.service_tier;
|
|
205
222
|
const currentText = cleaned.text && typeof cleaned.text === "object" && !Array.isArray(cleaned.text) ? { ...cleaned.text } : void 0;
|
|
206
223
|
if (modelMeta?.supportVerbosity === false) {
|
|
207
224
|
if (currentText !== void 0) {
|
|
@@ -569,6 +586,8 @@ const MAX_REQUEST_IMAGE_BYTES = 20971520;
|
|
|
569
586
|
const REQUEST_IMAGE_PIXEL_BUDGET = 4194304;
|
|
570
587
|
const REQUEST_IMAGE_MAX_BYTES = 1048576;
|
|
571
588
|
const MAX_LOG_FILE_BYTES = 524288;
|
|
589
|
+
const MAX_PERSISTED_SESSIONS = 200;
|
|
590
|
+
const DEFAULT_SESSION_KEY = "__default__";
|
|
572
591
|
function resolvePluginCacheDir(codexHome) {
|
|
573
592
|
return join(codexHome, PLUGIN_CACHE_DIR_NAME);
|
|
574
593
|
}
|
|
@@ -613,7 +632,23 @@ function writeBackupCache(codexHome, raw) {
|
|
|
613
632
|
writeFileAtomicSync(join(resolvePluginCacheDir(codexHome), BACKUP_CACHE_FILE_NAME), `${JSON.stringify(raw, null, 2)}\n`);
|
|
614
633
|
} catch {}
|
|
615
634
|
}
|
|
635
|
+
function readPersistedSessionFastModes(codexHome) {
|
|
636
|
+
const result = new Map();
|
|
637
|
+
try {
|
|
638
|
+
const entries = JSON.parse(readFileSync(join(resolvePluginCacheDir(codexHome), RUNTIME_STATE_FILE_NAME), "utf8"))?.sessionFastModes;
|
|
639
|
+
if (entries && typeof entries === "object" && !Array.isArray(entries)) {
|
|
640
|
+
for (const [k, v] of Object.entries(entries)) if (typeof k === "string" && k !== "" && typeof v === "boolean") result.set(k, v);
|
|
641
|
+
}
|
|
642
|
+
} catch {}
|
|
643
|
+
return result;
|
|
644
|
+
}
|
|
645
|
+
function writePersistedSessionFastModes(codexHome, sessionFastModes) {
|
|
646
|
+
try {
|
|
647
|
+
writeFileAtomicSync(join(resolvePluginCacheDir(codexHome), RUNTIME_STATE_FILE_NAME), `${JSON.stringify({ sessionFastModes: Object.fromEntries(sessionFastModes) }, null, 2)}\n`);
|
|
648
|
+
} catch {}
|
|
649
|
+
}
|
|
616
650
|
const Config = z.object({
|
|
651
|
+
fastMode: z.boolean().default(false).description("新会话默认是否开启 Fast 极速模式(未填时默认 false;单会话可在右下角限额弹窗内独立切换)").volatile(),
|
|
617
652
|
disabledModels: z.array(z.string()).default([...DEFAULT_DISABLED_MODELS]).description("在模型选择器中屏蔽的模型 slug 列表").volatile(),
|
|
618
653
|
contextWindows: z.dict(z.number()).default({}).description("按模型 slug 覆盖上下文窗口大小(tokens),未配置时默认使用 872000").volatile()
|
|
619
654
|
});
|
|
@@ -651,8 +686,32 @@ function apply(ctx, config = {}) {
|
|
|
651
686
|
const baseModels = baseProvider.getModels();
|
|
652
687
|
const retryPolicy = resolveRetryPolicy(void 0, "codex-subscription retryPolicy");
|
|
653
688
|
const getCodexHome = () => resolveCodexHome();
|
|
689
|
+
const getConfigFastMode = () => readVolatile(config.fastMode, false) === true;
|
|
654
690
|
const getDisabledModels = () => readVolatile(config.disabledModels, DEFAULT_DISABLED_MODELS);
|
|
655
691
|
const getContextWindows = () => readVolatile(config.contextWindows, {});
|
|
692
|
+
const sessionFastModes = readPersistedSessionFastModes(getCodexHome());
|
|
693
|
+
const getEffectiveFastMode = (sessionId) => {
|
|
694
|
+
const key = normalizeCodexSessionId(sessionId);
|
|
695
|
+
if (key !== void 0 && sessionFastModes.has(key)) return sessionFastModes.get(key) === true;
|
|
696
|
+
if (key === void 0 && sessionFastModes.has(DEFAULT_SESSION_KEY)) return sessionFastModes.get(DEFAULT_SESSION_KEY) === true;
|
|
697
|
+
return getConfigFastMode();
|
|
698
|
+
};
|
|
699
|
+
const setSessionFastMode = (sessionId, nextFastMode) => {
|
|
700
|
+
const codexHome = getCodexHome();
|
|
701
|
+
const key = normalizeCodexSessionId(sessionId) ?? DEFAULT_SESSION_KEY;
|
|
702
|
+
sessionFastModes.delete(key);
|
|
703
|
+
sessionFastModes.set(key, nextFastMode);
|
|
704
|
+
while (sessionFastModes.size > MAX_PERSISTED_SESSIONS) {
|
|
705
|
+
const oldest = sessionFastModes.keys().next().value;
|
|
706
|
+
if (oldest === void 0) break;
|
|
707
|
+
sessionFastModes.delete(oldest);
|
|
708
|
+
}
|
|
709
|
+
writePersistedSessionFastModes(codexHome, sessionFastModes);
|
|
710
|
+
appendPluginLog(codexHome, ctx, "INFO", "fast_mode.updated", {
|
|
711
|
+
sessionId: key,
|
|
712
|
+
fastMode: nextFastMode
|
|
713
|
+
});
|
|
714
|
+
};
|
|
656
715
|
const authManager = createAuthManager({
|
|
657
716
|
getCodexHome,
|
|
658
717
|
getClientVersion: () => ensureSnapshot().catalog.clientVersion,
|
|
@@ -690,7 +749,7 @@ function apply(ctx, config = {}) {
|
|
|
690
749
|
transport: "sse",
|
|
691
750
|
headers: buildCodexHeaders(rest.headers, catalog.clientVersion),
|
|
692
751
|
async onPayload(payload, requestModel) {
|
|
693
|
-
return sanitizeCodexPayload(await onPayload?.(payload, requestModel) ?? payload, modelMeta);
|
|
752
|
+
return sanitizeCodexPayload(await onPayload?.(payload, requestModel) ?? payload, modelMeta, getEffectiveFastMode(normalizedSessionId));
|
|
694
753
|
}
|
|
695
754
|
};
|
|
696
755
|
}
|
|
@@ -854,12 +913,28 @@ function apply(ctx, config = {}) {
|
|
|
854
913
|
if (typeof registerFetch !== "function") return;
|
|
855
914
|
const registerRoute = () => registerFetch.call(connection?.fetch, {
|
|
856
915
|
path: USAGE_ROUTE_PATH,
|
|
857
|
-
methods: ["GET"],
|
|
916
|
+
methods: ["GET", "POST"],
|
|
858
917
|
requestBody: "buffered",
|
|
859
918
|
fetch: async (request) => {
|
|
919
|
+
if (request.method === "POST") {
|
|
920
|
+
const body = await request.json().catch(() => null);
|
|
921
|
+
if (typeof body?.fastMode === "boolean") setSessionFastMode(typeof body.sessionId === "string" ? body.sessionId : void 0, body.fastMode);
|
|
922
|
+
return new Response(JSON.stringify({
|
|
923
|
+
ok: true,
|
|
924
|
+
defaultFastMode: getConfigFastMode(),
|
|
925
|
+
sessionFastModes: Object.fromEntries(sessionFastModes)
|
|
926
|
+
}), {
|
|
927
|
+
status: 200,
|
|
928
|
+
headers: { "content-type": "application/json" }
|
|
929
|
+
});
|
|
930
|
+
}
|
|
860
931
|
const force = new URL(request.url, "http://localhost").searchParams.get("force") === "1";
|
|
861
932
|
const result = await usageFetcher.getUsage(force);
|
|
862
|
-
return new Response(JSON.stringify(
|
|
933
|
+
return new Response(JSON.stringify({
|
|
934
|
+
...result,
|
|
935
|
+
defaultFastMode: getConfigFastMode(),
|
|
936
|
+
sessionFastModes: Object.fromEntries(sessionFastModes)
|
|
937
|
+
}), {
|
|
863
938
|
status: 200,
|
|
864
939
|
headers: { "content-type": "application/json" }
|
|
865
940
|
});
|
|
@@ -900,4 +975,4 @@ const __test = Object.freeze({
|
|
|
900
975
|
createUsageFetcher
|
|
901
976
|
});
|
|
902
977
|
//#endregion
|
|
903
|
-
export { BACKUP_CACHE_FILE_NAME, Config, LOG_FILE_NAME, PLUGIN_CACHE_DIR_NAME, __test, apply, inject, name };
|
|
978
|
+
export { BACKUP_CACHE_FILE_NAME, Config, LOG_FILE_NAME, PLUGIN_CACHE_DIR_NAME, RUNTIME_STATE_FILE_NAME, __test, apply, inject, name };
|