dsh-codex-subscription 1.14.3 → 1.14.4

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.
Files changed (3) hide show
  1. package/lib/client.js +2147 -2004
  2. package/lib/index.js +93 -24
  3. package/package.json +3 -3
package/lib/index.js CHANGED
@@ -1345,7 +1345,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
1345
1345
  }
1346
1346
  //#endregion
1347
1347
  //#region src/version.js
1348
- const PACKAGE_VERSION = "1.14.3";
1348
+ const PACKAGE_VERSION = "1.14.4";
1349
1349
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
1350
1350
  //#endregion
1351
1351
  //#region src/model-catalog.js
@@ -1429,10 +1429,13 @@ function createOfficialModelCatalog(options = {}) {
1429
1429
  let revision = 0;
1430
1430
  let refreshing;
1431
1431
  let generation = 0;
1432
+ let refreshStatus = "idle";
1432
1433
  const refresh = ({ signal } = {}) => {
1433
1434
  if (signal?.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("Codex model catalog refresh aborted"));
1434
1435
  if (refreshing?.generation === generation) return refreshing.promise;
1435
1436
  const currentGeneration = generation;
1437
+ refreshStatus = "refreshing";
1438
+ let outcome = "idle";
1436
1439
  const controller = new AbortController();
1437
1440
  const abort = () => {
1438
1441
  if (!controller.signal.aborted) controller.abort(signal?.reason ?? /* @__PURE__ */ new Error("Codex model catalog refresh aborted"));
@@ -1464,7 +1467,10 @@ function createOfficialModelCatalog(options = {}) {
1464
1467
  signal: requestSignal
1465
1468
  });
1466
1469
  if (currentGeneration !== generation || requestSignal.aborted) return false;
1467
- if (response.status === 304) return false;
1470
+ if (response.status === 304) {
1471
+ outcome = "ok";
1472
+ return false;
1473
+ }
1468
1474
  if (!response.ok) throw new Error(`Codex model catalog failed (HTTP ${response.status})`);
1469
1475
  const remote = parseOfficialModelCatalog(await response.json());
1470
1476
  if (currentGeneration !== generation || requestSignal.aborted) return false;
@@ -1477,6 +1483,7 @@ function createOfficialModelCatalog(options = {}) {
1477
1483
  metadata = new Map(remote.map((model) => [model.id, model]));
1478
1484
  etag = nonEmpty$2(response.headers.get("etag")) ?? etag;
1479
1485
  revision += 1;
1486
+ outcome = "ok";
1480
1487
  return true;
1481
1488
  })();
1482
1489
  let rejectAborted;
@@ -1487,11 +1494,17 @@ function createOfficialModelCatalog(options = {}) {
1487
1494
  });
1488
1495
  timer = scheduleTimeout(() => controller.abort(timeoutError), timeoutMs);
1489
1496
  timer.unref?.();
1490
- const promise = Promise.race([work, abortPromise]).finally(() => {
1497
+ const promise = Promise.race([work, abortPromise]).catch((error) => {
1498
+ outcome = "failed";
1499
+ throw error;
1500
+ }).finally(() => {
1491
1501
  cancelTimeout(timer);
1492
1502
  signal?.removeEventListener("abort", abort);
1493
1503
  requestSignal.removeEventListener("abort", rejectAborted);
1494
- if (refreshing?.promise === promise) refreshing = void 0;
1504
+ if (refreshing?.promise === promise) {
1505
+ refreshing = void 0;
1506
+ refreshStatus = outcome;
1507
+ }
1495
1508
  });
1496
1509
  refreshing = {
1497
1510
  generation: currentGeneration,
@@ -1505,6 +1518,10 @@ function createOfficialModelCatalog(options = {}) {
1505
1518
  getModels: (fallback) => models ?? fallback,
1506
1519
  metadata: (modelId) => metadata.get(modelId),
1507
1520
  revision: () => revision,
1521
+ status: () => ({
1522
+ source: models === void 0 ? "fallback" : "online",
1523
+ refresh: refreshStatus
1524
+ }),
1508
1525
  clear() {
1509
1526
  generation += 1;
1510
1527
  const flight = refreshing;
@@ -1513,6 +1530,7 @@ function createOfficialModelCatalog(options = {}) {
1513
1530
  models = void 0;
1514
1531
  metadata = /* @__PURE__ */ new Map();
1515
1532
  etag = void 0;
1533
+ refreshStatus = "idle";
1516
1534
  revision += 1;
1517
1535
  }
1518
1536
  });
@@ -2268,7 +2286,8 @@ var OriginalImageStore = class {
2268
2286
  await Promise.all([assertPrivateFile(metadataFile), assertPrivateFile(originalFile)]);
2269
2287
  const metadata = parseMetadata(await readFile(metadataFile, "utf8"));
2270
2288
  if (metadata === void 0 || metadata.image.assetId !== assetId || metadata.sessionId !== sessionId && !originalImageRefsEqual(metadata.image, inherited)) return void 0;
2271
- const data = new Uint8Array(await readFile(originalFile));
2289
+ const buffer = await readFile(originalFile);
2290
+ const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
2272
2291
  const dimensions = pngDimensions(data);
2273
2292
  if (data.byteLength !== metadata.image.bytes || digest(data) !== metadata.image.sha256 || dimensions.width !== metadata.image.width || dimensions.height !== metadata.image.height) return void 0;
2274
2293
  return {
@@ -2284,10 +2303,11 @@ var OriginalImageStore = class {
2284
2303
  const stored = await this.read(sessionId, assetId, inherited);
2285
2304
  if (stored === void 0 || offset >= stored.data.byteLength || offset % 4194304 !== 0) return void 0;
2286
2305
  const end = Math.min(stored.data.byteLength, offset + ORIGINAL_IMAGE_CHUNK_BYTES);
2306
+ const chunk = Buffer.from(stored.data.buffer, stored.data.byteOffset + offset, end - offset);
2287
2307
  return {
2288
2308
  ref: stored.ref,
2289
2309
  offset,
2290
- encoded: Buffer.from(stored.data.subarray(offset, end)).toString("base64"),
2310
+ encoded: chunk.toString("base64"),
2291
2311
  done: end === stored.data.byteLength
2292
2312
  };
2293
2313
  }
@@ -2297,6 +2317,7 @@ var OriginalImageStore = class {
2297
2317
  const requestAreas = /* @__PURE__ */ new Set([
2298
2318
  "login",
2299
2319
  "model",
2320
+ "catalog",
2300
2321
  "quota",
2301
2322
  "quota-reset",
2302
2323
  "search",
@@ -2342,7 +2363,7 @@ function safeRequests(network) {
2342
2363
  return result;
2343
2364
  }
2344
2365
  /** Build a support report that deliberately excludes OAuth and account metadata. */
2345
- async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" }, network }) {
2366
+ async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" }, network, modelCatalog }) {
2346
2367
  let account = { status: "unknown" };
2347
2368
  const issues = [];
2348
2369
  try {
@@ -2351,6 +2372,7 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
2351
2372
  issues.push({ code: "account-status-unavailable" });
2352
2373
  }
2353
2374
  const preference = preferences.status();
2375
+ const catalog = modelCatalog?.status?.();
2354
2376
  return {
2355
2377
  schemaVersion: 3,
2356
2378
  package: "dsh-codex-subscription",
@@ -2363,6 +2385,15 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
2363
2385
  account,
2364
2386
  login,
2365
2387
  requests: safeRequests(network),
2388
+ ...catalog && ["fallback", "online"].includes(catalog.source) && [
2389
+ "idle",
2390
+ "refreshing",
2391
+ "ok",
2392
+ "failed"
2393
+ ].includes(catalog.refresh) ? { catalog: {
2394
+ source: catalog.source,
2395
+ refresh: catalog.refresh
2396
+ } } : {},
2366
2397
  configuration: {
2367
2398
  contextMode: preference.contextMode,
2368
2399
  quickQuotaMode: preference.quickQuotaMode,
@@ -2749,40 +2780,67 @@ function forecastUsage(usage, state = { windows: {} }, now = Date.now(), options
2749
2780
  function createQuotaForecastReader({ reader, enabled, now = Date.now, scope = () => "default", stateStore }) {
2750
2781
  let state = { windows: {} };
2751
2782
  let loaded = false;
2783
+ let loading;
2784
+ let generation = 0;
2785
+ let historyGeneration = 0;
2786
+ let persistence = Promise.resolve();
2787
+ const persist = (operation) => {
2788
+ const pending = persistence.then(operation);
2789
+ persistence = pending.catch(() => {});
2790
+ return pending;
2791
+ };
2752
2792
  const load = async () => {
2753
2793
  if (loaded) return;
2794
+ if (loading) return loading;
2795
+ const current = historyGeneration;
2796
+ const pending = Promise.resolve().then(() => stateStore?.load?.()).then((restored) => {
2797
+ if (current !== historyGeneration) return;
2798
+ if (restored?.windows !== null && typeof restored?.windows === "object") state = restored;
2799
+ loaded = true;
2800
+ }).finally(() => {
2801
+ if (loading === pending) loading = void 0;
2802
+ });
2803
+ loading = pending;
2804
+ return pending;
2805
+ };
2806
+ const clearHistory = (clearReader = true) => {
2807
+ generation += 1;
2808
+ historyGeneration += 1;
2809
+ state = { windows: {} };
2754
2810
  loaded = true;
2755
- const restored = await stateStore?.load?.();
2756
- if (restored?.windows !== null && typeof restored?.windows === "object") state = restored;
2811
+ if (clearReader) reader.clear();
2812
+ return persist(() => stateStore?.clear?.());
2757
2813
  };
2758
2814
  return Object.freeze({
2759
2815
  async read(options) {
2816
+ const current = generation;
2817
+ const account = await scope();
2760
2818
  const usage = await reader.read(options);
2819
+ if (current !== generation) return usage;
2761
2820
  await load();
2821
+ const activeAccount = await scope();
2822
+ if (current !== generation || account !== activeAccount) return usage;
2762
2823
  if (!enabled()) {
2763
- state = { windows: {} };
2764
- await stateStore?.clear?.();
2824
+ await clearHistory(false);
2765
2825
  return usage;
2766
2826
  }
2767
- const forecast = forecastUsage(usage, state, now(), { scope: await scope() });
2827
+ const forecast = forecastUsage(usage, state, now(), { scope: account });
2768
2828
  state = forecast.state;
2769
- if (forecast.changed) await stateStore?.save?.(state);
2770
- return forecast.usage;
2771
- },
2772
- async clear() {
2773
- state = { windows: {} };
2774
- loaded = true;
2775
- reader.clear();
2776
- await stateStore?.clear?.();
2829
+ if (forecast.changed) await persist(() => stateStore?.save?.(forecast.state));
2830
+ return current === generation ? forecast.usage : usage;
2777
2831
  },
2832
+ clear: () => clearHistory(),
2778
2833
  clearCache() {
2834
+ generation += 1;
2779
2835
  reader.clear();
2780
2836
  },
2781
2837
  async clearScope(targetScope) {
2838
+ generation += 1;
2782
2839
  await load();
2783
2840
  const prefix = `[${JSON.stringify(cleanSegment(targetScope))},`;
2784
2841
  state = { windows: Object.fromEntries(Object.entries(state.windows).filter(([key]) => !key.startsWith(prefix))) };
2785
- await stateStore?.save?.(state);
2842
+ const snapshot = state;
2843
+ await persist(() => stateStore?.save?.(snapshot));
2786
2844
  }
2787
2845
  });
2788
2846
  }
@@ -3547,12 +3605,22 @@ function apply(ctx) {
3547
3605
  stateStore: new QuotaForecastStateStore({ filename: dshHomePath("state", "codex-subscription", "quota-forecast.json") })
3548
3606
  });
3549
3607
  ctx.effect(() => {
3608
+ let forecasting = false;
3550
3609
  const warmForecast = (value) => {
3551
- if (normalizeQuickQuotaMode(value["quickQuotaMode"], value["quickQuotaVisible"]) !== "forecast") return;
3610
+ if (!(normalizeQuickQuotaMode(value["quickQuotaMode"], value["quickQuotaVisible"]) === "forecast")) {
3611
+ if (forecasting) usageReader.clear().catch((error) => ctx.logger?.debug?.("could not clear Codex quota forecast: %s", error.message));
3612
+ forecasting = false;
3613
+ return;
3614
+ }
3615
+ forecasting = true;
3552
3616
  usageReader.read().catch((error) => ctx.logger?.debug?.("could not warm Codex quota forecast: %s", error.message));
3553
3617
  };
3554
3618
  warmForecast(settings.get());
3555
- return settings.watch(warmForecast);
3619
+ const unwatch = settings.watch(warmForecast);
3620
+ return () => {
3621
+ unwatch();
3622
+ usageReader.clearCache();
3623
+ };
3556
3624
  }, "codex-subscription: quota forecast warm-up");
3557
3625
  const resetCreditService = createCodexResetCreditService({
3558
3626
  getAuth: resolveAuth,
@@ -3569,7 +3637,8 @@ function apply(ctx) {
3569
3637
  auth,
3570
3638
  preferences,
3571
3639
  login: coordinator.supportState(),
3572
- network
3640
+ network,
3641
+ modelCatalog
3573
3642
  }),
3574
3643
  modelCatalog,
3575
3644
  originalImages,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "1.14.3",
3
+ "version": "1.14.4",
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",
@@ -127,8 +127,8 @@
127
127
  },
128
128
  "scripts": {
129
129
  "build": "tsdown --config tsdown.config.mjs",
130
- "test:behavior": "node --test tests/auth-security.test.mjs tests/cache-contract.test.mjs tests/codex-images.test.mjs tests/codex-search.test.mjs tests/image-edit.test.mjs tests/model-catalog.test.mjs tests/oauth-network.test.mjs tests/pi-ai-runtime.test.mjs tests/plugin-integration.test.mjs tests/reset-credits.test.mjs tests/sidebar-quota.test.mjs tests/transport-contract.test.mjs tests/usage.test.mjs",
131
- "test:delivery": "node --test tests/ci-change-plan.test.mjs tests/client-contract.test.mjs tests/prepare-compat-release.test.mjs tests/publish-idempotency.test.mjs tests/release-contract.test.mjs tests/release-notes.test.mjs tests/support-intake.test.mjs",
130
+ "test:behavior": "node scripts/run-tests.mjs behavior",
131
+ "test:delivery": "node scripts/run-tests.mjs delivery",
132
132
  "test": "node --test tests/*.test.mjs",
133
133
  "check": "pnpm run test && pnpm run build && pnpm pack --pack-destination .artifacts"
134
134
  }