dsh-codex-subscription 1.11.0 → 1.11.1

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/AGENTS.md CHANGED
@@ -6,7 +6,7 @@ Use this guide when a user asks an Agent to install, update, verify, or remove
6
6
  ## Safety
7
7
 
8
8
  - Confirm the target DSH installation and profile. Use `web` only when it is the user's target.
9
- - Use the exact `1.11.0` package below; do not install a moving branch.
9
+ - Use the exact `1.11.1` package below; do not install a moving branch.
10
10
  - Never print OAuth credentials, account IDs, authorization callbacks, or credential-store contents.
11
11
  - Preserve the DSH profile, unrelated plugins, sessions, and saved sign-in.
12
12
  - Do not start, stop, or restart DSH without explicit permission.
@@ -34,13 +34,13 @@ For an official DSH installation run through npm, keep the complete `npx` prefix
34
34
  run command does not create a global `dsh` command:
35
35
 
36
36
  ```sh
37
- npx -y @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add dsh-codex-subscription@1.11.0
37
+ npx -y @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add dsh-codex-subscription@1.11.1
38
38
  ```
39
39
 
40
40
  With an existing global `dsh` command:
41
41
 
42
42
  ```sh
43
- dsh plugin --profile web add dsh-codex-subscription@1.11.0
43
+ dsh plugin --profile web add dsh-codex-subscription@1.11.1
44
44
  ```
45
45
 
46
46
  Use the same `add` command to update or repair. This is the complete package-changing operation.
@@ -58,7 +58,7 @@ dsh --profile web --dump-config
58
58
 
59
59
  For the official npm route, run the same checks with `npx -y @deepseek-ai/dsh@0.1.1-rc.2` in place of `dsh`.
60
60
 
61
- 1. `dsh-codex-subscription` version `1.11.0` appears exactly once.
61
+ 1. `dsh-codex-subscription` version `1.11.1` appears exactly once.
62
62
  2. `codex-subscription` appears exactly once in the composed config.
63
63
  3. No unrelated plugin or profile was changed and DSH was not restarted.
64
64
 
package/README.md CHANGED
@@ -12,7 +12,7 @@ No OpenAI API key or Codex CLI. Models, search, quota, and image generation stay
12
12
  [![MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
13
13
  [![Star](https://img.shields.io/github/stars/WSL043/dsh-codex-subscription?style=flat&logo=github&label=Star)](https://github.com/WSL043/dsh-codex-subscription/stargazers)
14
14
 
15
- [Three-step start](#three-step-start) · [Install](#install) · [Update and uninstall](#update-and-uninstall) · [简体中文](https://github.com/WSL043/dsh-codex-subscription/blob/main/README.zh-CN.md)
15
+ [Three-step start](#three-step-start) · [Install](#install) · [Contribute](CONTRIBUTING.md) · [Update and uninstall](#update-and-uninstall) · [简体中文](https://github.com/WSL043/dsh-codex-subscription/blob/main/README.zh-CN.md)
16
16
 
17
17
  </div>
18
18
 
@@ -211,6 +211,7 @@ The ChatGPT Codex backend and DSH can change independently. This community proje
211
211
 
212
212
  Use the [bug report form](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml) for project feedback.
213
213
  Use the [feature request form](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=feature-request.yml) for focused product suggestions.
214
+ Focused fixes and compatibility improvements are welcome; see [CONTRIBUTING.md](CONTRIBUTING.md).
214
215
  For DSH plugin discussion, visit [DeepSeek Harness Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions).
215
216
  Read [SECURITY.md](SECURITY.md) before reporting sensitive issues.
216
217
 
package/README.zh-CN.md CHANGED
@@ -13,7 +13,7 @@
13
13
  [![MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
14
14
  [![Star](https://img.shields.io/github/stars/WSL043/dsh-codex-subscription?style=flat&logo=github&label=Star)](https://github.com/WSL043/dsh-codex-subscription/stargazers)
15
15
 
16
- [三步开始](#三步开始) · [安装](#安装) · [更新与卸载](#更新与卸载) · [English](https://github.com/WSL043/dsh-codex-subscription/blob/main/README.md)
16
+ [三步开始](#三步开始) · [安装](#安装) · [参与贡献](CONTRIBUTING.md) · [更新与卸载](#更新与卸载) · [English](https://github.com/WSL043/dsh-codex-subscription/blob/main/README.md)
17
17
 
18
18
  </div>
19
19
 
@@ -139,7 +139,7 @@ dsh --profile web --dump-config
139
139
  ### 输入框额度
140
140
 
141
141
  <p align="center">
142
- <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/composer-quota-en.png" width="800" alt="输入框内的 Codex 剩余额度">
142
+ <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/composer-quota.png" width="800" alt="中文 DSH 输入框内的 Codex 剩余额度进度条">
143
143
  </p>
144
144
 
145
145
  可在设置中选择关闭、百分比、进度条或 Beta 续航预测;紧凑额度只在选择 Codex 模型时显示。续航预测仅在用户主动选择后,根据本机运行内存中观察到的官方剩余百分比估算当前消耗速度;样本不足时保持安静,额度重置、登录变化或关闭功能后会重新校准。普通 Codex 使用服务端返回窗口中剩余最少的一项,
@@ -209,6 +209,7 @@ ChatGPT Codex 后端和 DSH 可能独立变化;本项目为社区项目,与
209
209
 
210
210
  本项目的问题反馈请使用[使用问题表单](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml);
211
211
  明确的产品建议请使用[功能建议表单](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=feature-request.yml);
212
+ 欢迎提交聚焦的修复和兼容性改进,具体要求见 [CONTRIBUTING.md](CONTRIBUTING.md);
212
213
  DSH 插件交流可前往 [DeepSeek Harness Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions)。
213
214
  敏感问题请先阅读 [SECURITY.md](SECURITY.md)。
214
215
 
package/lib/client.js CHANGED
@@ -318,6 +318,7 @@ window.__ModuleLoader__.load({
318
318
  resetTooEarly: "请等待冷静期结束后再确认。",
319
319
  resetAcknowledgeRequired: "请先确认已了解这次操作可能消耗重置次数。",
320
320
  resetAccountChanged: "登录账号已变更,请重新开始。",
321
+ resetUncertain: "服务端返回结果不确定。请再次确认,插件会复用同一个请求,不会另外发起一次重置。",
321
322
  creditsNote: "仅显示 Codex 为此账户或工作区实际返回的额外 Credits、消费上限或额度重置次数;三者不是同一项。",
322
323
  creditsUsed: "已用 {used} / {limit} credits",
323
324
  spendReached: "Credits 月度消费上限已用尽。",
@@ -483,6 +484,7 @@ window.__ModuleLoader__.load({
483
484
  resetTooEarly: "Wait for the cooldown before confirming.",
484
485
  resetAcknowledgeRequired: "Confirm that you understand this may consume a reset.",
485
486
  resetAccountChanged: "The signed-in account changed. Start again.",
487
+ resetUncertain: "The server result is uncertain. Confirm again to check the same request; the plugin will not start a separate reset.",
486
488
  creditsNote: "Shows only extra Credits, spending caps, or quota resets returned for this account or workspace; these are separate items.",
487
489
  creditsUsed: "{used} / {limit} credits used",
488
490
  spendReached: "The monthly Credits spending cap has been reached.",
@@ -2310,7 +2312,8 @@ window.__ModuleLoader__.load({
2310
2312
  ["This quota reset is already in progress", "resetInProgress"],
2311
2313
  ["Wait before confirming this quota reset", "resetTooEarly"],
2312
2314
  ["You must acknowledge that this may consume one quota reset", "resetAcknowledgeRequired"],
2313
- ["The signed-in ChatGPT account changed", "resetAccountChanged"]
2315
+ ["The signed-in ChatGPT account changed", "resetAccountChanged"],
2316
+ ["Quota reset result is uncertain; retry this confirmation to check the same request", "resetUncertain"]
2314
2317
  ])).get(error instanceof Error ? error.message : "") ?? "resetFailed");
2315
2318
  }
2316
2319
  function UsageCard({ rpc, t, signedIn, resetKey }) {
package/lib/index.js CHANGED
@@ -936,7 +936,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
936
936
  }
937
937
  //#endregion
938
938
  //#region src/version.js
939
- const PACKAGE_VERSION = "1.11.0";
939
+ const PACKAGE_VERSION = "1.11.1";
940
940
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
941
941
  //#endregion
942
942
  //#region src/model-catalog.js
@@ -2156,6 +2156,7 @@ const DEFAULT_CONFIRM_DELAY_MS = 5e3;
2156
2156
  const DEFAULT_CHALLENGE_TTL_MS = 6e4;
2157
2157
  const DEFAULT_TIMEOUT_MS = 15e3;
2158
2158
  const MAX_COPY_LENGTH = 240;
2159
+ const UNCERTAIN_RESET_RESULT = "Quota reset result is uncertain; retry this confirmation to check the same request";
2159
2160
  const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
2160
2161
  const requestSignal = (signal, timeoutMs) => {
2161
2162
  const timeout = AbortSignal.timeout(timeoutMs);
@@ -2232,8 +2233,8 @@ function createCodexResetCreditService(options) {
2232
2233
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
2233
2234
  const challenges = /* @__PURE__ */ new Map();
2234
2235
  const resolveCredentials = async (signal) => credentialsOf(await getAuth({ signal }), await readCredential({ signal }));
2235
- const readDetails = async (signal) => {
2236
- const { access, accountId } = await resolveCredentials(signal);
2236
+ const readDetails = async (signal, credentials) => {
2237
+ const { access, accountId } = credentials ?? await resolveCredentials(signal);
2237
2238
  const response = await fetchReset(CODEX_RESET_CREDITS_URL, {
2238
2239
  method: "GET",
2239
2240
  redirect: "error",
@@ -2267,7 +2268,24 @@ function createCodexResetCreditService(options) {
2267
2268
  };
2268
2269
  },
2269
2270
  async prepare({ signal } = {}) {
2270
- const { accountId, details } = await readDetails(signal);
2271
+ const credentials = await resolveCredentials(signal);
2272
+ for (const [challengeId, challenge] of challenges) {
2273
+ if (challenge.accountId !== credentials.accountId || challenge.uncertain !== true) continue;
2274
+ if (now() > challenge.expiresAt) {
2275
+ challenges.delete(challengeId);
2276
+ continue;
2277
+ }
2278
+ return {
2279
+ challengeId,
2280
+ availableCount: challenge.availableCount,
2281
+ readyAt: challenge.readyAt,
2282
+ expiresAt: challenge.expiresAt,
2283
+ ...challenge.creditExpiresAt === void 0 ? {} : { creditExpiresAt: challenge.creditExpiresAt },
2284
+ ...challenge.title === void 0 ? {} : { title: challenge.title },
2285
+ ...challenge.description === void 0 ? {} : { description: challenge.description }
2286
+ };
2287
+ }
2288
+ const { accountId, details } = await readDetails(signal, credentials);
2271
2289
  const preparedAt = now();
2272
2290
  const readyAt = preparedAt + confirmDelayMs;
2273
2291
  const expiresAt = Math.min(preparedAt + challengeTtlMs, details.creditExpiresAt ?? Number.MAX_SAFE_INTEGER);
@@ -2279,7 +2297,12 @@ function createCodexResetCreditService(options) {
2279
2297
  creditId: details.creditId,
2280
2298
  redeemRequestId: randomUUID$1(),
2281
2299
  readyAt,
2282
- expiresAt
2300
+ expiresAt,
2301
+ availableCount: details.availableCount,
2302
+ creditExpiresAt: details.creditExpiresAt,
2303
+ title: details.title,
2304
+ description: details.description,
2305
+ uncertain: false
2283
2306
  });
2284
2307
  return {
2285
2308
  challengeId,
@@ -2302,38 +2325,66 @@ function createCodexResetCreditService(options) {
2302
2325
  }
2303
2326
  if (acknowledged !== true) throw new Error("You must acknowledge that this may consume one quota reset");
2304
2327
  challenge.state = "pending";
2328
+ let retryable = challenge.uncertain === true;
2305
2329
  try {
2306
2330
  const { access, accountId } = await resolveCredentials(signal);
2307
- if (accountId !== challenge.accountId) throw new Error("The signed-in ChatGPT account changed");
2308
- const response = await fetchReset(CODEX_RESET_CONSUME_URL, {
2309
- method: "POST",
2310
- redirect: "error",
2311
- headers: {
2312
- authorization: `Bearer ${access}`,
2313
- "chatgpt-account-id": accountId,
2314
- accept: "application/json",
2315
- "content-type": "application/json",
2316
- "cache-control": "no-store",
2317
- "user-agent": USER_AGENT
2318
- },
2319
- body: JSON.stringify({
2320
- redeem_request_id: challenge.redeemRequestId,
2321
- credit_id: challenge.creditId
2322
- }),
2323
- signal: requestSignal(signal, timeoutMs)
2324
- });
2325
- if (!response.ok) throw new Error(response.status === 401 || response.status === 403 ? "ChatGPT sign-in needs to be renewed" : `ChatGPT quota reset request failed (HTTP ${response.status})`);
2331
+ if (accountId !== challenge.accountId) {
2332
+ retryable = false;
2333
+ throw new Error("The signed-in ChatGPT account changed");
2334
+ }
2335
+ let response;
2336
+ try {
2337
+ response = await fetchReset(CODEX_RESET_CONSUME_URL, {
2338
+ method: "POST",
2339
+ redirect: "error",
2340
+ headers: {
2341
+ authorization: `Bearer ${access}`,
2342
+ "chatgpt-account-id": accountId,
2343
+ accept: "application/json",
2344
+ "content-type": "application/json",
2345
+ "cache-control": "no-store",
2346
+ "user-agent": USER_AGENT
2347
+ },
2348
+ body: JSON.stringify({
2349
+ redeem_request_id: challenge.redeemRequestId,
2350
+ credit_id: challenge.creditId
2351
+ }),
2352
+ signal: requestSignal(signal, timeoutMs)
2353
+ });
2354
+ } catch {
2355
+ retryable = true;
2356
+ throw new Error(UNCERTAIN_RESET_RESULT);
2357
+ }
2358
+ if (!response.ok) {
2359
+ if (response.status >= 500) {
2360
+ retryable = true;
2361
+ throw new Error(UNCERTAIN_RESET_RESULT);
2362
+ }
2363
+ if (response.status !== 401 && response.status !== 403) retryable = false;
2364
+ throw new Error(response.status === 401 || response.status === 403 ? "ChatGPT sign-in needs to be renewed" : `ChatGPT quota reset request failed (HTTP ${response.status})`);
2365
+ }
2326
2366
  let raw;
2327
2367
  try {
2328
2368
  raw = await response.json();
2329
2369
  } catch {
2330
- throw new Error("ChatGPT returned an unreadable quota reset response");
2370
+ retryable = true;
2371
+ throw new Error(UNCERTAIN_RESET_RESULT);
2331
2372
  }
2332
- const result = parseConsumeResult(raw);
2373
+ let result;
2374
+ try {
2375
+ result = parseConsumeResult(raw);
2376
+ } catch {
2377
+ retryable = true;
2378
+ throw new Error(UNCERTAIN_RESET_RESULT);
2379
+ }
2380
+ retryable = false;
2333
2381
  usageReader.clear();
2334
2382
  return result;
2335
2383
  } finally {
2336
- challenges.delete(challengeId);
2384
+ if (retryable && now() <= challenge.expiresAt) {
2385
+ challenge.state = "prepared";
2386
+ challenge.uncertain = true;
2387
+ } else challenges.delete(challengeId);
2337
2388
  }
2338
2389
  },
2339
2390
  clear() {
@@ -2347,7 +2398,6 @@ const name = "codex-subscription";
2347
2398
  const inject = [
2348
2399
  "llm",
2349
2400
  "credentials",
2350
- "connection",
2351
2401
  "settings",
2352
2402
  "web",
2353
2403
  "loader",
@@ -2723,7 +2773,7 @@ function apply(ctx) {
2723
2773
  ctx.effect(() => {
2724
2774
  modelCatalog.refresh().catch((error) => ctx.logger?.debug?.("could not refresh Codex model catalog: %s", error.message));
2725
2775
  }, "codex-subscription: official model catalog");
2726
- ctx.effect(() => ctx.connection.rpc.handle(CHANNEL, handler, { authority: "trusted-host" }), "codex-subscription: DSH-trusted account RPC");
2776
+ ctx.inject(["connection"], (connectionContext) => connectionContext.effect(() => connectionContext.connection.rpc.handle(CHANNEL, handler, { authority: "trusted-host" }), "codex-subscription: DSH-trusted account RPC"));
2727
2777
  }
2728
2778
  //#endregion
2729
2779
  export { CODEX_IMAGE_GENERATION_URL, CODEX_IMAGE_TOOL_NAME, CODEX_RESET_CONSUME_URL, CODEX_RESET_CREDITS_URL, CODEX_USAGE_URL, CodexLoginCoordinator, DshOAuthCredentialStore, apply, assertCodexAuthUrl, commandForCodexAuthUrl, createCodexAuthService, createCodexImageTool, createCodexResetCreditService, createCodexRpcHandler, createCodexUsageReader, createSearchProviderSwitcher, createSubscriptionDiagnostics, createSubscriptionRpcHandler, decodeCodexPng, inject, name, normalizeContextMode, normalizeCustomContextWindow, openCodexAuthUrl, parseCodexUsage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "1.11.0",
3
+ "version": "1.11.1",
4
4
  "description": "Use ChatGPT and Codex subscriptions in DeepSeek Harness with OAuth, quota, safe resets, web search, images, and Fast mode",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",