dsh-codex-subscription 2.2.0 → 2.2.2

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/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { createHash, randomBytes, randomUUID } from "node:crypto";
3
- import { closeOpenAICodexWebSocketSessions, resetOpenAICodexWebSocketDebugStats } from "@earendil-works/pi-ai/api/openai-codex-responses";
3
+ import { closeOpenAICodexWebSocketSessions, getOpenAICodexWebSocketDebugStats, resetOpenAICodexWebSocketDebugStats } from "@earendil-works/pi-ai/api/openai-codex-responses";
4
4
  import { execFile, spawn } from "node:child_process";
5
5
  import { request } from "node:https";
6
6
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -553,14 +553,15 @@ async function withCodexNetwork(run, options = {}) {
553
553
  }
554
554
  if (options.websocket) activeWebSocketScopes += 1;
555
555
  if (activeScopes === 0) {
556
- baseFetch = globalThis.fetch;
556
+ const fetchBeforeScope = globalThis.fetch;
557
+ baseFetch = fetchBeforeScope;
557
558
  scopedFetch = async (input, init) => {
558
559
  const scope = networkScope.getStore();
559
- if (scope === void 0) return baseFetch(input, init);
560
+ if (scope === void 0) return fetchBeforeScope(input, init);
560
561
  const { options: scopedOptions, allowedHosts, resolved } = scope;
561
562
  const proxyFetch = scopedOptions.fetchThroughProxy ?? fetchThroughProxy;
562
563
  const target = new URL(typeof input === "string" || input instanceof URL ? input : input.url);
563
- if (target.protocol !== "https:" || !allowedHosts.has(target.hostname)) return baseFetch(input, init);
564
+ if (target.protocol !== "https:" || !allowedHosts.has(target.hostname)) return fetchBeforeScope(input, init);
564
565
  let proxy = resolved.get(target.hostname);
565
566
  if (proxy === void 0) {
566
567
  proxy = resolveCodexProxy({
@@ -571,7 +572,7 @@ async function withCodexNetwork(run, options = {}) {
571
572
  }
572
573
  const route = await proxy;
573
574
  scopedOptions.onRoute?.(route.source);
574
- const response = await (route.url === void 0 ? baseFetch(input, init) : proxyFetch(input, init, route.url));
575
+ const response = await (route.url === void 0 ? fetchBeforeScope(input, init) : proxyFetch(input, init, route.url));
575
576
  return scopedOptions.transformResponse?.(response, target) ?? response;
576
577
  };
577
578
  globalThis.fetch = scopedFetch;
@@ -661,6 +662,21 @@ function createSubscriptionConnection({ resolveMode = () => "sse", resolveProxy
661
662
  const namespace = randomUUID();
662
663
  const sessions = /* @__PURE__ */ new Set();
663
664
  return {
665
+ snapshot() {
666
+ const totals = Object.fromEntries([
667
+ "requests",
668
+ "connectionsCreated",
669
+ "connectionsReused",
670
+ "deltaRequests",
671
+ "websocketFailures",
672
+ "sseFallbacks"
673
+ ].map((key) => [key, 0]));
674
+ for (const session of sessions) {
675
+ const stats = getOpenAICodexWebSocketDebugStats(session);
676
+ for (const key of Object.keys(totals)) if (Number.isSafeInteger(stats?.[key]) && stats[key] >= 0) totals[key] = Math.min(Number.MAX_SAFE_INTEGER, totals[key] + stats[key]);
677
+ }
678
+ return totals;
679
+ },
664
680
  async prepare(options = {}) {
665
681
  if (resolveMode() !== "websocket") return { options: {
666
682
  ...options,
@@ -715,6 +731,11 @@ const MAX_BYTES = 2 * 1024 * 1024;
715
731
  const MAX_AGE_MS = 10080 * 60 * 1e3;
716
732
  function createCompactionBridge({ enabled = () => false, threshold = () => 1e5, accountScope, diagnostic = () => {}, now = Date.now }) {
717
733
  const scope = new AsyncLocalStorage();
734
+ const stats = {
735
+ requests: 0,
736
+ checkpointsSaved: 0,
737
+ checkpointsReused: 0
738
+ };
718
739
  const wrapStream = (factory, options) => (async function* () {
719
740
  if (!enabled() || options.provider !== "openai-codex") {
720
741
  yield* factory(options);
@@ -774,6 +795,7 @@ function createCompactionBridge({ enabled = () => false, threshold = () => 1e5,
774
795
  }
775
796
  }
776
797
  };
798
+ stats.checkpointsSaved = Math.min(Number.MAX_SAFE_INTEGER, stats.checkpointsSaved + 1);
777
799
  }
778
800
  yield event;
779
801
  }
@@ -782,6 +804,7 @@ function createCompactionBridge({ enabled = () => false, threshold = () => 1e5,
782
804
  }
783
805
  })();
784
806
  return {
807
+ snapshot: () => ({ ...stats }),
785
808
  wrapAdapter(adapter) {
786
809
  return new Proxy(adapter, { get(target, property) {
787
810
  if (property === "stream") return (options) => wrapStream((o) => target.stream(o), options);
@@ -799,6 +822,11 @@ function createCompactionBridge({ enabled = () => false, threshold = () => 1e5,
799
822
  preparePayload(payload, contextWindow) {
800
823
  const state = scope.getStore();
801
824
  if (!state) return payload;
825
+ if (!state.counted) {
826
+ state.counted = true;
827
+ stats.requests = Math.min(Number.MAX_SAFE_INTEGER, stats.requests + 1);
828
+ if (state.suffix) stats.checkpointsReused = Math.min(Number.MAX_SAFE_INTEGER, stats.checkpointsReused + 1);
829
+ }
802
830
  const configured = threshold();
803
831
  if (!Number.isSafeInteger(configured) || configured < 1e3) throw new Error("Invalid compaction threshold");
804
832
  const limit = Number.isFinite(contextWindow) && contextWindow >= 2e3 ? Math.min(configured, Math.floor(contextWindow / 2)) : configured;
@@ -901,6 +929,8 @@ const RPC_ENDPOINTS = Object.freeze([
901
929
  "runtime/install",
902
930
  "runtime/remove",
903
931
  "runtime/cancel",
932
+ "storage/status",
933
+ "storage/clear-forecast",
904
934
  "reset-credit/inspect",
905
935
  "reset-credit/prepare",
906
936
  "reset-credit/consume",
@@ -2831,7 +2861,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
2831
2861
  Object.freeze(["0.82.1", "0.85.1"]);
2832
2862
  //#endregion
2833
2863
  //#region src/version.js
2834
- const PACKAGE_VERSION = "2.2.0";
2864
+ const PACKAGE_VERSION = "2.2.2";
2835
2865
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
2836
2866
  //#endregion
2837
2867
  //#region src/model-catalog.js
@@ -2995,6 +3025,7 @@ function createOfficialModelCatalog(options = {}) {
2995
3025
  etag = nonEmpty$2(response.headers.get("etag")) ?? etag;
2996
3026
  revision += 1;
2997
3027
  outcome = "ok";
3028
+ options.onUpdated?.();
2998
3029
  return true;
2999
3030
  })();
3000
3031
  let rejectAborted;
@@ -3047,6 +3078,7 @@ function createOfficialModelCatalog(options = {}) {
3047
3078
  etag = void 0;
3048
3079
  refreshStatus = "idle";
3049
3080
  revision += 1;
3081
+ options.onUpdated?.();
3050
3082
  }
3051
3083
  });
3052
3084
  }
@@ -3699,7 +3731,7 @@ function createCodexImageTool(options) {
3699
3731
  localPath: options.originalImages.originalPath(original.assetId),
3700
3732
  ...metadata.background === void 0 ? {} : { background: metadata.background },
3701
3733
  ...metadata.quality === void 0 ? {} : { quality: metadata.quality },
3702
- ...metadata.size === void 0 ? {} : { size: metadata.size }
3734
+ size: `${original.width}x${original.height}`
3703
3735
  };
3704
3736
  if (exec.parent !== void 0) exec.deferContext(createUserMessage({
3705
3737
  content: imageContent(result),
@@ -3925,7 +3957,7 @@ function safeRequests(network) {
3925
3957
  return result;
3926
3958
  }
3927
3959
  /** Build a support report that deliberately excludes OAuth and account metadata. */
3928
- async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" }, network, modelCatalog }) {
3960
+ async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" }, network, modelCatalog, connection, compaction }) {
3929
3961
  let account = { status: "unknown" };
3930
3962
  const issues = [];
3931
3963
  try {
@@ -3963,6 +3995,19 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
3963
3995
  account,
3964
3996
  login,
3965
3997
  requests: safeRequests(network),
3998
+ ...connection ? { websocket: safeCounters(connection, [
3999
+ "requests",
4000
+ "connectionsCreated",
4001
+ "connectionsReused",
4002
+ "deltaRequests",
4003
+ "websocketFailures",
4004
+ "sseFallbacks"
4005
+ ]) } : {},
4006
+ ...compaction ? { compaction: safeCounters(compaction, [
4007
+ "requests",
4008
+ "checkpointsSaved",
4009
+ "checkpointsReused"
4010
+ ]) } : {},
3966
4011
  ...catalog && ["fallback", "online"].includes(catalog.source) && [
3967
4012
  "idle",
3968
4013
  "refreshing",
@@ -3985,6 +4030,10 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
3985
4030
  issues
3986
4031
  };
3987
4032
  }
4033
+ function safeCounters(source, keys) {
4034
+ const values = source.snapshot();
4035
+ return Object.fromEntries(keys.filter((key) => Number.isSafeInteger(values?.[key]) && values[key] >= 0).map((key) => [key, values[key]]));
4036
+ }
3988
4037
  //#endregion
3989
4038
  //#region src/usage.js
3990
4039
  const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
@@ -4682,6 +4731,7 @@ function createQuotaForecastReader({ reader, enabled, now = Date.now, scope = ()
4682
4731
  return current === generation ? forecast.usage : usage;
4683
4732
  },
4684
4733
  clear: () => clearHistory(),
4734
+ storage: () => stateStore.usage(),
4685
4735
  clearCache() {
4686
4736
  generation += 1;
4687
4737
  reader.clear();
@@ -4767,6 +4817,22 @@ var QuotaForecastStateStore = class {
4767
4817
  async clear() {
4768
4818
  await rm(this.filename, { force: true });
4769
4819
  }
4820
+ async usage() {
4821
+ try {
4822
+ const info = await lstat(this.filename);
4823
+ if (!info.isFile() || info.isSymbolicLink()) throw Error("Unexpected forecast cache entry");
4824
+ return {
4825
+ bytes: info.size,
4826
+ limit: this.maxBytes
4827
+ };
4828
+ } catch (error) {
4829
+ if (error.code === "ENOENT") return {
4830
+ bytes: 0,
4831
+ limit: this.maxBytes
4832
+ };
4833
+ throw error;
4834
+ }
4835
+ }
4770
4836
  };
4771
4837
  //#endregion
4772
4838
  //#region src/reset-credits.js
@@ -5065,6 +5131,17 @@ const publicError = (code, message) => ({
5065
5131
  });
5066
5132
  function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditService, preferences, runtimeManagement, diagnosticsReader, modelCatalog, originalImages, resolveInheritedOriginal, closeConnections, onAccountChanged }) {
5067
5133
  return async (endpoint, payload, signal) => {
5134
+ if (endpoint === "storage/status" || endpoint === "storage/clear-forecast") try {
5135
+ signal.throwIfAborted();
5136
+ if (endpoint === "storage/clear-forecast") await usageReader.clear();
5137
+ return {
5138
+ ok: true,
5139
+ value: await usageReader.storage()
5140
+ };
5141
+ } catch (error) {
5142
+ if (signal.aborted) throw error;
5143
+ return publicError("operation-error", "Could not manage forecast cache");
5144
+ }
5068
5145
  if ([
5069
5146
  "runtime/status",
5070
5147
  "runtime/install",
@@ -5328,6 +5405,7 @@ function apply(ctx, config = {}) {
5328
5405
  });
5329
5406
  ctx.effect(() => ctx.on?.("plugin-manager/install-state", (value) => runtimeManagement.progress(value)));
5330
5407
  const modelCatalog = createOfficialModelCatalog({
5408
+ onUpdated: () => ctx.llm.emitAdaptersUpdated?.(),
5331
5409
  getAuth: (options) => resolveAuth(options),
5332
5410
  readCredential: (options) => store.read(PROVIDER, options),
5333
5411
  baseModels: () => baseProvider.getModels(),
@@ -5634,7 +5712,9 @@ function apply(ctx, config = {}) {
5634
5712
  preferences,
5635
5713
  login: coordinator.supportState(),
5636
5714
  network,
5637
- modelCatalog
5715
+ modelCatalog,
5716
+ connection,
5717
+ compaction
5638
5718
  }),
5639
5719
  modelCatalog,
5640
5720
  closeConnections: () => connection.dispose(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
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",