@lyogvce/dsh-codex-subscription 0.2.6 → 0.2.7

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 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 订阅限额内联展示(`conversation.input.right`)**
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 模式(自动持久化至 `~/.codex/cache/dsh-codex-subscription/runtime-state.json`,也可在插件设置页通过 `fastMode` 配置),开启后胶囊上显示闪电标识 `⚡`,并在请求体中自动为支持 Fast 档位的模型注入 `"service_tier": "priority"`。
25
26
 
26
27
  ---
27
28
 
package/lib/client.js CHANGED
@@ -139,12 +139,14 @@ window.__ModuleLoader__.load({
139
139
  const ttlMs = options.ttlMs ?? 6e4;
140
140
  let state = {
141
141
  quota: null,
142
+ fastMode: false,
142
143
  loading: false,
143
144
  error: null
144
145
  };
145
146
  let inflight = null;
146
147
  let inflightForce = false;
147
148
  let requestSeq = 0;
149
+ let fastModeSeq = 0;
148
150
  const listeners = new Set();
149
151
  function setState(next) {
150
152
  state = next;
@@ -154,6 +156,7 @@ window.__ModuleLoader__.load({
154
156
  if (!force && state.quota !== null && nowFn() - state.quota.fetchedAt < ttlMs) return;
155
157
  if (inflight !== null && (!force || inflightForce)) return inflight;
156
158
  const seq = ++requestSeq;
159
+ const fastSeqAtStart = fastModeSeq;
157
160
  inflightForce = force;
158
161
  setState({
159
162
  ...state,
@@ -170,7 +173,7 @@ window.__ModuleLoader__.load({
170
173
  if (seq !== requestSeq) return;
171
174
  if (!response.ok) {
172
175
  setState({
173
- quota: state.quota,
176
+ ...state,
174
177
  loading: false,
175
178
  error: `HTTP ${response.status}`
176
179
  });
@@ -178,8 +181,10 @@ window.__ModuleLoader__.load({
178
181
  }
179
182
  const payload = await response.json();
180
183
  if (seq !== requestSeq) return;
184
+ const resolvedFastMode = fastSeqAtStart === fastModeSeq && typeof payload?.fastMode === "boolean" ? payload.fastMode : state.fastMode;
181
185
  if (payload && payload.ok === true && payload.quota) setState({
182
186
  quota: payload.quota,
187
+ fastMode: resolvedFastMode,
183
188
  loading: false,
184
189
  error: null
185
190
  });
@@ -188,6 +193,7 @@ window.__ModuleLoader__.load({
188
193
  const errMsg = payload && !payload.ok && typeof payload.error === "string" ? payload.error : "Invalid response";
189
194
  setState({
190
195
  quota: fallbackQuota ?? null,
196
+ fastMode: resolvedFastMode,
191
197
  loading: false,
192
198
  error: errMsg
193
199
  });
@@ -195,7 +201,7 @@ window.__ModuleLoader__.load({
195
201
  } catch (error) {
196
202
  if (seq !== requestSeq) return;
197
203
  setState({
198
- quota: state.quota,
204
+ ...state,
199
205
  loading: false,
200
206
  error: safeErrorMessage(error)
201
207
  });
@@ -209,6 +215,30 @@ window.__ModuleLoader__.load({
209
215
  inflight = task;
210
216
  return task;
211
217
  }
218
+ async function setFastMode(enabled) {
219
+ const nextFastMode = Boolean(enabled);
220
+ const seq = ++fastModeSeq;
221
+ setState({
222
+ ...state,
223
+ fastMode: nextFastMode
224
+ });
225
+ try {
226
+ const response = await fetchFn(USAGE_ROUTE_PATH, {
227
+ method: "POST",
228
+ headers: {
229
+ "Content-Type": "application/json",
230
+ Accept: "application/json"
231
+ },
232
+ body: JSON.stringify({ fastMode: nextFastMode })
233
+ });
234
+ if (seq !== fastModeSeq || !response.ok) return;
235
+ const payload = await response.json();
236
+ if (seq === fastModeSeq && typeof payload?.fastMode === "boolean") setState({
237
+ ...state,
238
+ fastMode: payload.fastMode
239
+ });
240
+ } catch {}
241
+ }
212
242
  return {
213
243
  getSnapshot: () => state,
214
244
  subscribe(listener) {
@@ -217,7 +247,8 @@ window.__ModuleLoader__.load({
217
247
  listeners.delete(listener);
218
248
  };
219
249
  },
220
- refresh
250
+ refresh,
251
+ setFastMode
221
252
  };
222
253
  }
223
254
  //#endregion
@@ -250,6 +281,15 @@ window.__ModuleLoader__.load({
250
281
  ".dsh-codex-quota-bar-fill[data-level=\"warn\"]{background:var(--dsw-alias-state-warn-primary)}",
251
282
  ".dsh-codex-quota-bar-fill[data-level=\"error\"]{background:var(--dsw-alias-state-error-primary)}",
252
283
  ".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}",
284
+ ".dsh-codex-quota-fast-bolt{color:var(--dsw-alias-state-warn-primary,#f59e0b);flex:none}",
285
+ ".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)}",
286
+ ".dsh-codex-fast-info{display:flex;flex-direction:column;gap:1px;min-width:0}",
287
+ ".dsh-codex-fast-title{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:500;color:var(--dsw-alias-label-primary)}",
288
+ ".dsh-codex-fast-desc{font-size:11px;line-height:15px;color:var(--dsw-alias-label-tertiary)}",
289
+ ".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}",
290
+ ".dsh-codex-fast-switch[data-checked=\"true\"]{background:var(--dsw-alias-brand-primary,#3b82f6)}",
291
+ ".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}",
292
+ ".dsh-codex-fast-switch[data-checked=\"true\"] .dsh-codex-fast-switch-thumb{transform:translateX(14px)}",
253
293
  ".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
294
  ].join("");
255
295
  //#endregion
@@ -265,7 +305,9 @@ window.__ModuleLoader__.load({
265
305
  "quota.refresh": "↻ 刷新",
266
306
  "quota.refreshing": "↻ 刷新中…",
267
307
  "quota.usedPrefix": "已用 ",
268
- "quota.remainPrefix": "剩余 "
308
+ "quota.remainPrefix": "剩余 ",
309
+ "quota.fastTitle": "Fast 极速模式",
310
+ "quota.fastDesc": "2x 推理速度,消耗更多限额"
269
311
  };
270
312
  const en = {
271
313
  "quota.title": "Codex Subscription Quota",
@@ -273,7 +315,9 @@ window.__ModuleLoader__.load({
273
315
  "quota.refresh": "↻ Refresh",
274
316
  "quota.refreshing": "↻ Refreshing…",
275
317
  "quota.usedPrefix": "Used ",
276
- "quota.remainPrefix": "Remaining "
318
+ "quota.remainPrefix": "Remaining ",
319
+ "quota.fastTitle": "Fast Mode",
320
+ "quota.fastDesc": "2x speed, increased usage"
277
321
  };
278
322
  const EMPTY_MODEL_STORE = {
279
323
  getSnapshot: () => null,
@@ -354,6 +398,7 @@ window.__ModuleLoader__.load({
354
398
  type: "button",
355
399
  className: "dsh-codex-quota-trigger",
356
400
  "data-level": level,
401
+ "data-fast": quotaSnap.fastMode ? "true" : void 0,
357
402
  "aria-label": `${t("quota.title")}: ${pillText}`,
358
403
  "aria-haspopup": "dialog",
359
404
  "aria-expanded": open,
@@ -380,7 +425,16 @@ window.__ModuleLoader__.load({
380
425
  r: 5.5,
381
426
  strokeDasharray: ringDash,
382
427
  transform: "rotate(-90 7 7)"
383
- })), h("span", { className: "dsh-codex-quota-text" }, pillText)), open ? h("div", {
428
+ })), quotaSnap.fastMode ? h("svg", {
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", {
384
438
  className: "dsh-codex-quota-popover",
385
439
  role: "dialog",
386
440
  "aria-label": t("quota.title"),
@@ -394,7 +448,26 @@ window.__ModuleLoader__.load({
394
448
  onClick: () => {
395
449
  quotaStore.refresh(true);
396
450
  }
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, quotaSnap.error !== null ? h("div", { className: "dsh-codex-quota-error" }, quotaSnap.error) : null) : null);
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("svg", {
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", {
461
+ type: "button",
462
+ role: "switch",
463
+ className: "dsh-codex-fast-switch",
464
+ "aria-checked": quotaSnap.fastMode,
465
+ "aria-label": t("quota.fastTitle"),
466
+ "data-checked": quotaSnap.fastMode ? "true" : "false",
467
+ onClick: () => {
468
+ quotaStore.setFastMode(!quotaSnap.fastMode);
469
+ }
470
+ }, h("span", { className: "dsh-codex-fast-switch-thumb" }))), quotaSnap.error !== null ? h("div", { className: "dsh-codex-quota-error" }, quotaSnap.error) : null) : null);
398
471
  }
399
472
  const inject = ["slots", "locale"];
400
473
  function apply(ctx) {
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;
@@ -91,6 +99,7 @@ interface CodexModelMetadata {
91
99
  readonly supportVerbosity: boolean;
92
100
  readonly defaultVerbosity: 'low' | 'medium' | 'high';
93
101
  readonly defaultReasoningLevel?: string | undefined;
102
+ readonly fastServiceTier?: string | undefined;
94
103
  }
95
104
  interface ParsedModelsCatalog {
96
105
  readonly clientVersion: string;
@@ -104,7 +113,7 @@ declare function parseModelsCache(rawCache: RawModelsCache | null | undefined, b
104
113
  declare function buildCodexUserAgent(version?: string): string;
105
114
  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,12 @@ interface CodexQuotaSnapshot {
123
132
  type CodexQuotaResponse = {
124
133
  readonly ok: true;
125
134
  readonly quota: CodexQuotaSnapshot;
135
+ readonly fastMode?: boolean | undefined;
126
136
  } | {
127
137
  readonly ok: false;
128
138
  readonly error: string;
129
139
  readonly quota?: CodexQuotaSnapshot | undefined;
140
+ readonly fastMode?: boolean | undefined;
130
141
  };
131
142
  interface UsageFetcherOptions {
132
143
  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",
@@ -81,6 +82,16 @@ function isVerbosity(value) {
81
82
  function isPositiveSafeInt(value) {
82
83
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
83
84
  }
85
+ function resolveFastServiceTier(entry) {
86
+ if (Array.isArray(entry.service_tiers)) {
87
+ 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"));
88
+ if (matched && typeof matched.id === "string") return matched.id.trim();
89
+ 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
+ return;
91
+ }
92
+ if (Array.isArray(entry.additional_speed_tiers) && entry.additional_speed_tiers.some((t) => typeof t === "string" && t.trim().toLowerCase() === "fast")) return "priority";
93
+ return "priority";
94
+ }
84
95
  function parseModelsCache(rawCache, baseModels, disabledModels = [], contextWindows = {}) {
85
96
  const disabledSet = new Set((Array.isArray(disabledModels) ? disabledModels : []).filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean));
86
97
  const clientVersion = typeof rawCache?.client_version === "string" && rawCache.client_version.trim() !== "" ? rawCache.client_version.trim() : DEFAULT_CLIENT_VERSION;
@@ -108,11 +119,13 @@ function parseModelsCache(rawCache, baseModels, disabledModels = [], contextWind
108
119
  const supportVerbosity = entry.support_verbosity !== false;
109
120
  const defaultVerbosity = isVerbosity(entry.default_verbosity) ? entry.default_verbosity : "low";
110
121
  const defaultReasoningLevel = normalizeDefaultReasoningLevel(entry.default_reasoning_level, thinkingLevelMap);
122
+ const fastServiceTier = resolveFastServiceTier(entry);
111
123
  metadataById.set(id, {
112
124
  contextWindow: targetContextWindow,
113
125
  supportVerbosity,
114
126
  defaultVerbosity,
115
- defaultReasoningLevel
127
+ defaultReasoningLevel,
128
+ fastServiceTier
116
129
  });
117
130
  models.push({
118
131
  ...base ?? {},
@@ -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 (cleaned.service_tier !== "priority" && cleaned.service_tier !== "ultrafast") delete cleaned.service_tier;
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) {
@@ -613,7 +630,21 @@ function writeBackupCache(codexHome, raw) {
613
630
  writeFileAtomicSync(join(resolvePluginCacheDir(codexHome), BACKUP_CACHE_FILE_NAME), `${JSON.stringify(raw, null, 2)}\n`);
614
631
  } catch {}
615
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;
639
+ }
640
+ }
641
+ function writePersistedFastMode(codexHome, fastMode) {
642
+ try {
643
+ writeFileAtomicSync(join(resolvePluginCacheDir(codexHome), RUNTIME_STATE_FILE_NAME), `${JSON.stringify({ fastMode }, null, 2)}\n`);
644
+ } catch {}
645
+ }
616
646
  const Config = z.object({
647
+ fastMode: z.boolean().default(false).description("开启 Fast 极速模式(注入 service_tier: priority,最高 2x 推理速度,消耗更多订阅限额)").volatile(),
617
648
  disabledModels: z.array(z.string()).default([...DEFAULT_DISABLED_MODELS]).description("在模型选择器中屏蔽的模型 slug 列表").volatile(),
618
649
  contextWindows: z.dict(z.number()).default({}).description("按模型 slug 覆盖上下文窗口大小(tokens),未配置时默认使用 872000").volatile()
619
650
  });
@@ -651,8 +682,21 @@ function apply(ctx, config = {}) {
651
682
  const baseModels = baseProvider.getModels();
652
683
  const retryPolicy = resolveRetryPolicy(void 0, "codex-subscription retryPolicy");
653
684
  const getCodexHome = () => resolveCodexHome();
685
+ const getConfigFastMode = () => Boolean(readVolatile(config.fastMode, false));
654
686
  const getDisabledModels = () => readVolatile(config.disabledModels, DEFAULT_DISABLED_MODELS);
655
687
  const getContextWindows = () => readVolatile(config.contextWindows, {});
688
+ let lastConfigFastMode = getConfigFastMode();
689
+ let fastModeOverride = readPersistedFastMode(getCodexHome());
690
+ const getEffectiveFastMode = () => fastModeOverride !== void 0 ? fastModeOverride : getConfigFastMode();
691
+ const setFastModeOverride = (nextFastMode, source) => {
692
+ const codexHome = getCodexHome();
693
+ fastModeOverride = nextFastMode;
694
+ writePersistedFastMode(codexHome, nextFastMode);
695
+ appendPluginLog(codexHome, ctx, "INFO", "fast_mode.updated", {
696
+ fastMode: nextFastMode,
697
+ source
698
+ });
699
+ };
656
700
  const authManager = createAuthManager({
657
701
  getCodexHome,
658
702
  getClientVersion: () => ensureSnapshot().catalog.clientVersion,
@@ -690,7 +734,7 @@ function apply(ctx, config = {}) {
690
734
  transport: "sse",
691
735
  headers: buildCodexHeaders(rest.headers, catalog.clientVersion),
692
736
  async onPayload(payload, requestModel) {
693
- return sanitizeCodexPayload(await onPayload?.(payload, requestModel) ?? payload, modelMeta);
737
+ return sanitizeCodexPayload(await onPayload?.(payload, requestModel) ?? payload, modelMeta, getEffectiveFastMode());
694
738
  }
695
739
  };
696
740
  }
@@ -854,12 +898,26 @@ function apply(ctx, config = {}) {
854
898
  if (typeof registerFetch !== "function") return;
855
899
  const registerRoute = () => registerFetch.call(connection?.fetch, {
856
900
  path: USAGE_ROUTE_PATH,
857
- methods: ["GET"],
901
+ methods: ["GET", "POST"],
858
902
  requestBody: "buffered",
859
903
  fetch: async (request) => {
904
+ if (request.method === "POST") {
905
+ const body = await request.json().catch(() => null);
906
+ if (typeof body?.fastMode === "boolean") setFastModeOverride(body.fastMode, "ui");
907
+ return new Response(JSON.stringify({
908
+ ok: true,
909
+ fastMode: getEffectiveFastMode()
910
+ }), {
911
+ status: 200,
912
+ headers: { "content-type": "application/json" }
913
+ });
914
+ }
860
915
  const force = new URL(request.url, "http://localhost").searchParams.get("force") === "1";
861
916
  const result = await usageFetcher.getUsage(force);
862
- return new Response(JSON.stringify(result), {
917
+ return new Response(JSON.stringify({
918
+ ...result,
919
+ fastMode: getEffectiveFastMode()
920
+ }), {
863
921
  status: 200,
864
922
  headers: { "content-type": "application/json" }
865
923
  });
@@ -871,6 +929,11 @@ function apply(ctx, config = {}) {
871
929
  if (typeof ctx.inject === "function") ctx.inject(["connection"], mountUsageRoute);
872
930
  else mountUsageRoute(ctx);
873
931
  ctx.on?.("loader/volatile-update", () => {
932
+ const currentConfigFastMode = getConfigFastMode();
933
+ if (currentConfigFastMode !== lastConfigFastMode) {
934
+ lastConfigFastMode = currentConfigFastMode;
935
+ setFastModeOverride(currentConfigFastMode, "config");
936
+ }
874
937
  ensureSnapshot();
875
938
  ctx.llm.emitAdaptersUpdated?.();
876
939
  });
@@ -900,4 +963,4 @@ const __test = Object.freeze({
900
963
  createUsageFetcher
901
964
  });
902
965
  //#endregion
903
- export { BACKUP_CACHE_FILE_NAME, Config, LOG_FILE_NAME, PLUGIN_CACHE_DIR_NAME, __test, apply, inject, name };
966
+ export { BACKUP_CACHE_FILE_NAME, Config, LOG_FILE_NAME, PLUGIN_CACHE_DIR_NAME, RUNTIME_STATE_FILE_NAME, __test, apply, inject, name };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lyogvce/dsh-codex-subscription",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "复用本机 ~/.codex 订阅凭证与 models_cache.json 模型缓存,在 DSH 中通过 SSE 直连调用 Codex 模型。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",