dsh-codex-subscription 3.0.0-beta.7 → 3.0.0-beta.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/lib/index.js CHANGED
@@ -1199,6 +1199,7 @@ const RPC_ENDPOINTS = Object.freeze([
1199
1199
  "account/select",
1200
1200
  "account/remove",
1201
1201
  "usage",
1202
+ "usage/accounts",
1202
1203
  "usage/history",
1203
1204
  "usage/profile",
1204
1205
  "diagnostics",
@@ -1400,7 +1401,9 @@ const SUPPORTED_RUNTIME_VERSIONS = Object.freeze([
1400
1401
  "0.2.0-rc.2",
1401
1402
  "0.2.1-alpha.1",
1402
1403
  "0.2.1-alpha.2",
1403
- "0.2.1-alpha.3"
1404
+ "0.2.1-alpha.3",
1405
+ "0.2.1-alpha.4",
1406
+ "0.2.1-alpha.5"
1404
1407
  ]);
1405
1408
  const require = createRequire(import.meta.url);
1406
1409
  const execute = promisify(execFile);
@@ -2158,9 +2161,36 @@ function inheritedOriginalImageRef(session, assetId) {
2158
2161
  }
2159
2162
  //#endregion
2160
2163
  //#region src/version.js
2161
- const PACKAGE_VERSION = "3.0.0-beta.7";
2164
+ const PACKAGE_VERSION = "3.0.0-beta.8";
2162
2165
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
2163
2166
  //#endregion
2167
+ //#region src/http-failure.js
2168
+ const SIGN_IN_RENEW = "ChatGPT sign-in needs to be renewed";
2169
+ const REQUEST_BLOCKED = "ChatGPT blocked this request before it reached your account (HTTP 403); your sign-in is fine, try again later or check your network, proxy or VPN";
2170
+ const CHALLENGE_MARKERS = /just a moment|cf-chl|_cf_chl_opt|attention required|enable javascript and cookies|challenge-platform/iu;
2171
+ /** Whether error text is a Cloudflare / network-protection page (pi-ai keeps only the body of a failed request, not its status). */
2172
+ const isChallengePage = (text) => typeof text === "string" && /^\s*<(?:!doctype|html)/iu.test(text) && CHALLENGE_MARKERS.test(text);
2173
+ /** Whether a 403 response is a Cloudflare / network-protection page rather than an answer from the ChatGPT backend. */
2174
+ async function isBlockedByNetwork(response) {
2175
+ if (response?.status !== 403) return false;
2176
+ try {
2177
+ const header = (name) => String(response.headers?.get?.(name) ?? "");
2178
+ if (/challenge/iu.test(header("cf-mitigated"))) return true;
2179
+ if (!/html/iu.test(header("content-type"))) return false;
2180
+ if (/cloudflare/iu.test(header("server"))) return true;
2181
+ const text = typeof response.clone === "function" ? (await response.clone().text()).slice(0, 8192) : "";
2182
+ return CHALLENGE_MARKERS.test(text);
2183
+ } catch {
2184
+ return false;
2185
+ }
2186
+ }
2187
+ /** The error message for a 401 or 403 answer, or undefined for any other status. */
2188
+ async function authFailureMessage(response) {
2189
+ if (response?.status === 401) return SIGN_IN_RENEW;
2190
+ if (response?.status !== 403) return void 0;
2191
+ return await isBlockedByNetwork(response) ? REQUEST_BLOCKED : SIGN_IN_RENEW;
2192
+ }
2193
+ //#endregion
2164
2194
  //#region src/codex-images.js
2165
2195
  const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
2166
2196
  const CODEX_IMAGE_GENERATION_URL = "https://chatgpt.com/backend-api/codex/images/generations";
@@ -2663,7 +2693,8 @@ function createCodexImageTool(options) {
2663
2693
  throw new Error(`Codex image ${editing ? "edit" : "generation"} request failed${requestFailureReason(error)}`, { cause: error });
2664
2694
  }
2665
2695
  if (!response.ok) {
2666
- if (response.status === 401 || response.status === 403) throw new Error("ChatGPT sign-in needs to be renewed");
2696
+ const authFailure = await authFailureMessage(response);
2697
+ if (authFailure !== void 0) throw new Error(authFailure);
2667
2698
  const requestId = supportId(response);
2668
2699
  if (response.status === 429) throw new Error(`${await limitMessage(response)}${requestId}`);
2669
2700
  throw new Error(`Codex image ${editing ? "edit" : "generation"} failed (HTTP ${response.status})${requestId}`);
@@ -4314,6 +4345,16 @@ function createDshRemoteControl({ controller, agents, permissions = () => void 0
4314
4345
  reasoningEffort: agent.options.reasoningEffort
4315
4346
  } : void 0;
4316
4347
  };
4348
+ const directoryOf = (session) => {
4349
+ try {
4350
+ const events = typeof session?.snapshotEvents === "function" ? session.snapshotEvents() : session?.eventsSnapshot;
4351
+ for (let index = (events?.length ?? 0) - 1; index >= 0; index -= 1) {
4352
+ const cwd = events[index]?.type === "working-directory/change" ? events[index].data?.cwd : void 0;
4353
+ if (typeof cwd === "string" && cwd !== "") return cwd;
4354
+ }
4355
+ } catch {}
4356
+ return session?.meta?.cwd;
4357
+ };
4317
4358
  const sameSelection = (a, b) => a?.provider === b?.provider && a?.model === b?.model && a?.reasoningEffort === b?.reasoningEffort;
4318
4359
  const selection = (agent) => {
4319
4360
  const saved = recorded(agent);
@@ -4435,16 +4476,17 @@ function createDshRemoteControl({ controller, agents, permissions = () => void 0
4435
4476
  const messages = session.deriveMessages();
4436
4477
  const first = messages.find((message) => typedText(message) !== "");
4437
4478
  const imageUrls = await imageUrlsOf(messages);
4479
+ const cwd = directoryOf(session);
4438
4480
  return {
4439
4481
  agent,
4440
4482
  thread: thread({
4441
4483
  id,
4442
- cwd: session.meta?.cwd,
4484
+ cwd,
4443
4485
  createdAt: session.meta?.createdAt,
4444
4486
  running: active.has(id),
4445
4487
  projectId: projectIdOf(id),
4446
4488
  preview: first ? visibleText(first.content).slice(0, 200) : "",
4447
- turns: withLiveTurn(id, projectTurns(messages, session.meta?.createdAt, imageUrls, session.meta?.cwd))
4489
+ turns: withLiveTurn(id, projectTurns(messages, session.meta?.createdAt, imageUrls, cwd))
4448
4490
  })
4449
4491
  };
4450
4492
  };
@@ -5341,7 +5383,7 @@ function createDshRemoteControl({ controller, agents, permissions = () => void 0
5341
5383
  }) : askPhone(request, "item/commandExecution/requestApproval", {
5342
5384
  ...common,
5343
5385
  command: commandText(session, request),
5344
- cwd: session.meta?.cwd ?? null,
5386
+ cwd: directoryOf(session) ?? null,
5345
5387
  reason,
5346
5388
  commandActions: [],
5347
5389
  availableDecisions: [
@@ -5475,7 +5517,7 @@ function createDshRemoteControl({ controller, agents, permissions = () => void 0
5475
5517
  type: "text",
5476
5518
  text: "x"
5477
5519
  }]
5478
- }, message], void 0, void 0, session.meta?.cwd).flatMap((turn) => turn.items.slice(1));
5520
+ }, message], void 0, void 0, directoryOf(session)).flatMap((turn) => turn.items.slice(1));
5479
5521
  const stream = streams.get(session.id);
5480
5522
  streams.delete(session.id);
5481
5523
  let reuse = stream?.item;
@@ -5772,6 +5814,42 @@ var DshOAuthAccountVault = class {
5772
5814
  return clone$1(payload?.accounts.find((account) => account.id === payload.activeId)?.credential);
5773
5815
  });
5774
5816
  }
5817
+ /** One account's credential, whether or not it is the active one. */
5818
+ readAccount(id) {
5819
+ return this.#enqueue(async () => {
5820
+ const payload = await this.#ensurePayload();
5821
+ return clone$1(payload?.accounts.find((account) => account.id === id)?.credential);
5822
+ });
5823
+ }
5824
+ /** Update one account's credential (a token refresh) without changing which account is active. */
5825
+ modifyAccount(id, update) {
5826
+ return this.#enqueue(async () => {
5827
+ let result;
5828
+ await this.#modifyPayload(async (current) => {
5829
+ const index = current.accounts.findIndex((account) => account.id === id);
5830
+ if (index < 0) throw new Error("Unknown Codex account");
5831
+ const previous = clone$1(current.accounts[index].credential);
5832
+ const next = await update(previous);
5833
+ if (next === void 0) {
5834
+ result = previous;
5835
+ return current;
5836
+ }
5837
+ const credential = sanitizeOAuthCredential(next);
5838
+ if (credential.email === void 0 && previous.email !== void 0) credential.email = previous.email;
5839
+ const accounts = [...current.accounts];
5840
+ accounts[index] = {
5841
+ ...accounts[index],
5842
+ credential
5843
+ };
5844
+ result = clone$1(credential);
5845
+ return {
5846
+ ...current,
5847
+ accounts
5848
+ };
5849
+ });
5850
+ return result;
5851
+ });
5852
+ }
5775
5853
  activeId() {
5776
5854
  return this.#enqueue(async () => (await this.#ensurePayload())?.activeId);
5777
5855
  }
@@ -5871,11 +5949,11 @@ var DshOAuthAccountVault = class {
5871
5949
  };
5872
5950
  //#endregion
5873
5951
  //#region src/credential-store.js
5874
- const PROVIDER$1 = "openai-codex";
5952
+ const PROVIDER$2 = "openai-codex";
5875
5953
  const abortIfNeeded = (options) => options?.signal?.throwIfAborted();
5876
5954
  const clone = (value) => value === void 0 ? void 0 : structuredClone(value);
5877
- function assertProvider(providerId) {
5878
- if (providerId !== PROVIDER$1) throw new Error(`Codex credential store does not own provider ${JSON.stringify(providerId)}`);
5955
+ function assertProvider$1(providerId) {
5956
+ if (providerId !== PROVIDER$2) throw new Error(`Codex credential store does not own provider ${JSON.stringify(providerId)}`);
5879
5957
  }
5880
5958
  function assertOAuthCredential(value) {
5881
5959
  if (value === void 0) return void 0;
@@ -5908,7 +5986,7 @@ var DshOAuthCredentialStore = class {
5908
5986
  this.vault = options.vault;
5909
5987
  }
5910
5988
  #enqueue(providerId, operation, options) {
5911
- assertProvider(providerId);
5989
+ assertProvider$1(providerId);
5912
5990
  const current = (this.#chains.get(providerId) ?? Promise.resolve()).catch(() => void 0).then(async () => {
5913
5991
  abortIfNeeded(options);
5914
5992
  return operation();
@@ -5921,7 +5999,7 @@ var DshOAuthCredentialStore = class {
5921
5999
  return current;
5922
6000
  }
5923
6001
  async #read(providerId, options) {
5924
- assertProvider(providerId);
6002
+ assertProvider$1(providerId);
5925
6003
  abortIfNeeded(options);
5926
6004
  if (this.vault !== void 0) {
5927
6005
  const current = await this.vault.readActive();
@@ -5954,8 +6032,8 @@ var DshOAuthCredentialStore = class {
5954
6032
  }
5955
6033
  async list(options) {
5956
6034
  abortIfNeeded(options);
5957
- return await this.read(PROVIDER$1, options) === void 0 ? [] : [{
5958
- providerId: PROVIDER$1,
6035
+ return await this.read(PROVIDER$2, options) === void 0 ? [] : [{
6036
+ providerId: PROVIDER$2,
5959
6037
  type: "oauth"
5960
6038
  }];
5961
6039
  }
@@ -6005,16 +6083,16 @@ function createCodexAuthService(models, store, options = {}) {
6005
6083
  const createPendingStore = options.createPendingStore ?? (() => new PendingOAuthCredentialStore());
6006
6084
  return Object.freeze({
6007
6085
  async status(options) {
6008
- const current = await store.read(PROVIDER$1, options);
6086
+ const current = await store.read(PROVIDER$2, options);
6009
6087
  const accounts = await accountVault?.list();
6010
6088
  if (current === void 0) return {
6011
6089
  authenticated: false,
6012
- provider: PROVIDER$1,
6090
+ provider: PROVIDER$2,
6013
6091
  ...accounts === void 0 ? {} : { accounts }
6014
6092
  };
6015
6093
  return {
6016
6094
  authenticated: true,
6017
- provider: PROVIDER$1,
6095
+ provider: PROVIDER$2,
6018
6096
  type: "oauth",
6019
6097
  expiresAt: current.expires,
6020
6098
  ...accounts === void 0 ? {} : { accounts }
@@ -6025,13 +6103,13 @@ function createCodexAuthService(models, store, options = {}) {
6025
6103
  if (accountVault === void 0 || createLoginModels === void 0) throw new Error("Codex multi-account is unavailable");
6026
6104
  return runLogin(async () => {
6027
6105
  const pending = createPendingStore();
6028
- await createLoginModels(pending).login(PROVIDER$1, "oauth", interaction);
6106
+ await createLoginModels(pending).login(PROVIDER$2, "oauth", interaction);
6029
6107
  const credential = pending.credential();
6030
6108
  if (credential === void 0) throw new Error("Codex login did not return credentials");
6031
6109
  await accountVault.add(input.label, credential);
6032
6110
  });
6033
6111
  }
6034
- return runLogin(() => models.login(PROVIDER$1, "oauth", interaction));
6112
+ return runLogin(() => models.login(PROVIDER$2, "oauth", interaction));
6035
6113
  },
6036
6114
  async select(id) {
6037
6115
  if (accountVault === void 0) throw new Error("Codex multi-account is unavailable");
@@ -6044,7 +6122,7 @@ function createCodexAuthService(models, store, options = {}) {
6044
6122
  return this.status();
6045
6123
  },
6046
6124
  logout(options) {
6047
- return models.logout(PROVIDER$1, options);
6125
+ return models.logout(PROVIDER$2, options);
6048
6126
  }
6049
6127
  });
6050
6128
  }
@@ -6580,6 +6658,13 @@ function normalizeTransportEvent(event, signal) {
6580
6658
  if (signal?.aborted || event?.type !== "error" || event.reason !== "error") return event;
6581
6659
  const message = event.error?.errorMessage;
6582
6660
  if (typeof message !== "string") return event;
6661
+ if (isChallengePage(message)) return {
6662
+ ...event,
6663
+ error: {
6664
+ ...event.error,
6665
+ errorMessage: REQUEST_BLOCKED
6666
+ }
6667
+ };
6583
6668
  if (!TRANSIENT_WEBSOCKET_CLOSE.test(message) && !GATEWAY_REPLAY_BUFFER_EXCEEDED.test(message)) return event;
6584
6669
  return {
6585
6670
  ...event,
@@ -6896,7 +6981,8 @@ Object.freeze([
6896
6981
  "0.85.1",
6897
6982
  "0.87.1",
6898
6983
  "1.1.0",
6899
- "1.2.0"
6984
+ "1.2.0",
6985
+ "1.3.0"
6900
6986
  ]);
6901
6987
  //#endregion
6902
6988
  //#region src/model-catalog.js
@@ -7303,7 +7389,10 @@ function createCodexSearchProvider(options) {
7303
7389
  if (signal?.aborted || error?.name === "AbortError") throw new WebError("Codex search aborted", "WEB_ABORTED", { cause: error });
7304
7390
  throw new WebError("Codex search request failed", "WEB_PROVIDER_ERROR", { cause: error });
7305
7391
  }
7306
- if (!response.ok) throw response.status === 401 || response.status === 403 ? new WebError("ChatGPT sign-in needs to be renewed", "WEB_PROVIDER_CREDENTIAL_MISSING") : new WebError(`Codex search request failed (HTTP ${response.status})`, "WEB_PROVIDER_ERROR");
7392
+ if (!response.ok) {
7393
+ const authFailure = await authFailureMessage(response);
7394
+ throw authFailure === "ChatGPT sign-in needs to be renewed" ? new WebError(authFailure, "WEB_PROVIDER_CREDENTIAL_MISSING") : authFailure !== void 0 ? new WebError(authFailure, "WEB_PROVIDER_ERROR") : new WebError(`Codex search request failed (HTTP ${response.status})`, "WEB_PROVIDER_ERROR");
7395
+ }
7307
7396
  let value;
7308
7397
  try {
7309
7398
  value = await response.json();
@@ -8302,7 +8391,7 @@ function createCodexUsageReader(options) {
8302
8391
  signal: requestSignal$1(signal, timeoutMs)
8303
8392
  });
8304
8393
  if (!response.ok) {
8305
- const error = /* @__PURE__ */ new Error(response.status === 401 || response.status === 403 ? "ChatGPT sign-in needs to be renewed" : `ChatGPT usage request failed (HTTP ${response.status})`);
8394
+ const error = new Error(await authFailureMessage(response) ?? `ChatGPT usage request failed (HTTP ${response.status})`);
8306
8395
  Object.defineProperty(error, "retryAfterMs", { value: retryAfterMs(response, now, maxRetryAfterMs) });
8307
8396
  throw error;
8308
8397
  }
@@ -8353,6 +8442,102 @@ function createCodexUsageReader(options) {
8353
8442
  });
8354
8443
  }
8355
8444
  //#endregion
8445
+ //#region src/account-usage.js
8446
+ const PROVIDER$1 = "openai-codex";
8447
+ const MAX_PARALLEL = 4;
8448
+ const assertProvider = (providerId) => {
8449
+ if (providerId !== PROVIDER$1) throw new Error("Codex account usage received an unknown provider");
8450
+ };
8451
+ /** pi-ai's OAuth credential store, bound to one account of the vault. */
8452
+ function accountCredentialStore(vault, id, expirySkewMs = 0) {
8453
+ const shift = (credential) => credential === void 0 || expirySkewMs === 0 ? credential : {
8454
+ ...credential,
8455
+ expires: credential.expires - expirySkewMs
8456
+ };
8457
+ return {
8458
+ async read(providerId) {
8459
+ assertProvider(providerId);
8460
+ return shift(await vault.readAccount(id));
8461
+ },
8462
+ async list() {
8463
+ return await vault.readAccount(id) === void 0 ? [] : [{
8464
+ providerId: PROVIDER$1,
8465
+ type: "oauth"
8466
+ }];
8467
+ },
8468
+ async modify(providerId, update) {
8469
+ assertProvider(providerId);
8470
+ return vault.modifyAccount(id, async (current) => update(shift(current)));
8471
+ },
8472
+ async delete() {
8473
+ throw new Error("An account usage read cannot remove a sign-in");
8474
+ }
8475
+ };
8476
+ }
8477
+ /**
8478
+ * @param {object} options
8479
+ * @param {{ list(): Promise<object[]>, readAccount(id: string): Promise<object|undefined>, modifyAccount: Function }} options.vault
8480
+ * @param {(credentials: object) => object} options.createModels builds a pi-ai Models over a credential store, with the Codex provider set
8481
+ * @param {typeof fetch} options.fetch
8482
+ */
8483
+ function createAccountUsageReader({ vault, createModels, fetch, expirySkewMs = 0, now = Date.now, activeReader }) {
8484
+ const readers = /* @__PURE__ */ new Map();
8485
+ const readerFor = (id) => {
8486
+ let reader = readers.get(id);
8487
+ if (reader === void 0) {
8488
+ const models = createModels(accountCredentialStore(vault, id, expirySkewMs));
8489
+ reader = createCodexUsageReader({
8490
+ getAuth: () => models.getAuth(PROVIDER$1),
8491
+ readCredential: () => vault.readAccount(id).then((credential) => credential),
8492
+ fetch,
8493
+ now
8494
+ });
8495
+ readers.set(id, reader);
8496
+ }
8497
+ return reader;
8498
+ };
8499
+ const summarize = (error, signal) => {
8500
+ if (signal?.aborted) throw error;
8501
+ const message = error instanceof Error ? error.message : "";
8502
+ return ["ChatGPT subscription is not signed in", "ChatGPT sign-in needs to be renewed"].includes(message) || message.includes("blocked this request") ? message : "Could not read ChatGPT usage";
8503
+ };
8504
+ return Object.freeze({
8505
+ /** One entry per account: its usage reading, or the reason it could not be read. */
8506
+ async readAll({ force = false, signal } = {}) {
8507
+ const accounts = await vault.list();
8508
+ for (const id of [...readers.keys()]) if (!accounts.some((account) => account.id === id)) readers.delete(id);
8509
+ const entries = new Array(accounts.length);
8510
+ let next = 0;
8511
+ const worker = async () => {
8512
+ while (next < accounts.length) {
8513
+ const index = next++;
8514
+ const account = accounts[index];
8515
+ try {
8516
+ const reader = account.active && activeReader !== void 0 ? activeReader : readerFor(account.id);
8517
+ entries[index] = {
8518
+ id: account.id,
8519
+ usage: await reader.read({
8520
+ force,
8521
+ signal
8522
+ })
8523
+ };
8524
+ } catch (error) {
8525
+ entries[index] = {
8526
+ id: account.id,
8527
+ error: summarize(error, signal)
8528
+ };
8529
+ }
8530
+ }
8531
+ };
8532
+ await Promise.all(Array.from({ length: Math.min(MAX_PARALLEL, accounts.length) }, worker));
8533
+ return entries;
8534
+ },
8535
+ clear() {
8536
+ readers.clear();
8537
+ }
8538
+ });
8539
+ }
8540
+ //#endregion
8356
8541
  //#region src/plan-history.js
8357
8542
  const CODEX_PLAN_HISTORY_URL = "https://chatgpt.com/backend-api/wham/usage/plan_limit_history?days=7";
8358
8543
  const DIMENSIONS = Object.freeze([
@@ -8432,7 +8617,7 @@ function createReportReader({ url, parse, label, getAuth, readCredential, fetch:
8432
8617
  available: false,
8433
8618
  fetchedAt: now()
8434
8619
  };
8435
- if (!response.ok) throw new Error(response.status === 401 ? "ChatGPT sign-in needs to be renewed" : response.status === 403 ? `ChatGPT does not offer the ${label} to this account` : `ChatGPT ${label} request failed (HTTP ${response.status})`);
8620
+ if (!response.ok) throw new Error(response.status === 401 ? SIGN_IN_RENEW : response.status === 403 ? await isBlockedByNetwork(response) ? REQUEST_BLOCKED : `ChatGPT does not offer the ${label} to this account` : `ChatGPT ${label} request failed (HTTP ${response.status})`);
8436
8621
  let body;
8437
8622
  try {
8438
8623
  body = await response.json();
@@ -8613,8 +8798,8 @@ function createConnectorsClient({ getAuth, readCredential, fetch: fetchRpc = fet
8613
8798
  }),
8614
8799
  signal: signal === void 0 ? timeout : AbortSignal.any([signal, timeout])
8615
8800
  });
8616
- if (response.status === 401) throw new Error("ChatGPT sign-in needs to be renewed");
8617
- if (response.status === 403) throw new Error("ChatGPT does not offer connectors to this account");
8801
+ if (response.status === 401) throw new Error(SIGN_IN_RENEW);
8802
+ if (response.status === 403) throw new Error(await isBlockedByNetwork(response) ? REQUEST_BLOCKED : "ChatGPT does not offer connectors to this account");
8618
8803
  if (!response.ok) throw new Error(`ChatGPT connectors request failed (HTTP ${response.status})`);
8619
8804
  const message = await readRpc(response);
8620
8805
  if (record$1(message.error)) throw new Error(`Connector error: ${clip(oneLine(String(message.error.message ?? message.error.code ?? "failed")), 200)}`);
@@ -9545,7 +9730,7 @@ function createCodexResetCreditService(options) {
9545
9730
  },
9546
9731
  signal: requestSignal(signal, timeoutMs)
9547
9732
  });
9548
- 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})`);
9733
+ if (!response.ok) throw new Error(await authFailureMessage(response) ?? `ChatGPT quota reset request failed (HTTP ${response.status})`);
9549
9734
  let raw;
9550
9735
  try {
9551
9736
  raw = await response.json();
@@ -9679,7 +9864,7 @@ function createCodexResetCreditService(options) {
9679
9864
  throw new Error(UNCERTAIN_RESET_RESULT);
9680
9865
  }
9681
9866
  if (response.status !== 401 && response.status !== 403) retryable = false;
9682
- throw new Error(response.status === 401 || response.status === 403 ? "ChatGPT sign-in needs to be renewed" : `ChatGPT quota reset request failed (HTTP ${response.status})`);
9867
+ throw new Error(await authFailureMessage(response) ?? `ChatGPT quota reset request failed (HTTP ${response.status})`);
9683
9868
  }
9684
9869
  let raw;
9685
9870
  try {
@@ -9735,7 +9920,7 @@ async function settlesLive(preferences, patch, signal, { attempts = 8, delayMs =
9735
9920
  }
9736
9921
  return live();
9737
9922
  }
9738
- function createSubscriptionRpcHandler({ authHandler, usageReader, planHistoryReader, usageProfileReader, resetCreditService, preferences, defaultModel, runtimeManagement, remoteControl, diagnosticsReader, modelCatalog, originalImages, resolveInheritedOriginal, closeConnections, onAccountChanged, onCleanupFailure }) {
9923
+ function createSubscriptionRpcHandler({ authHandler, usageReader, accountUsageReader, planHistoryReader, usageProfileReader, resetCreditService, preferences, defaultModel, runtimeManagement, remoteControl, diagnosticsReader, modelCatalog, originalImages, resolveInheritedOriginal, closeConnections, onAccountChanged, onCleanupFailure }) {
9739
9924
  return async (endpoint, payload, signal) => {
9740
9925
  if (endpoint === "default-model/status" || endpoint === "default-model/select") try {
9741
9926
  signal.throwIfAborted();
@@ -9904,7 +10089,11 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, planHistoryRea
9904
10089
  };
9905
10090
  } catch (error) {
9906
10091
  if (signal.aborted) throw error;
9907
- const known = /* @__PURE__ */ new Set(["ChatGPT subscription is not signed in", "ChatGPT sign-in needs to be renewed"]);
10092
+ const known = /* @__PURE__ */ new Set([
10093
+ "ChatGPT subscription is not signed in",
10094
+ "ChatGPT sign-in needs to be renewed",
10095
+ REQUEST_BLOCKED
10096
+ ]);
9908
10097
  const message = error instanceof Error && known.has(error.message) ? error.message : "Could not read ChatGPT usage";
9909
10098
  return publicError("internal", message);
9910
10099
  }
@@ -9919,9 +10108,26 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, planHistoryRea
9919
10108
  };
9920
10109
  } catch (error) {
9921
10110
  if (signal.aborted) throw error;
9922
- const known = /* @__PURE__ */ new Set(["ChatGPT subscription is not signed in", "ChatGPT sign-in needs to be renewed"]);
10111
+ const known = /* @__PURE__ */ new Set([
10112
+ "ChatGPT subscription is not signed in",
10113
+ "ChatGPT sign-in needs to be renewed",
10114
+ REQUEST_BLOCKED
10115
+ ]);
9923
10116
  return publicError("internal", error instanceof Error && known.has(error.message) ? error.message : "Could not read ChatGPT usage profile");
9924
10117
  }
10118
+ if (endpoint === "usage/accounts") try {
10119
+ signal.throwIfAborted();
10120
+ return {
10121
+ ok: true,
10122
+ value: { accounts: await accountUsageReader.readAll({
10123
+ force: payload?.force === true,
10124
+ signal
10125
+ }) }
10126
+ };
10127
+ } catch (error) {
10128
+ if (signal.aborted) throw error;
10129
+ return publicError("internal", "Could not read the usage of the signed-in accounts");
10130
+ }
9925
10131
  if (endpoint === "usage/history") try {
9926
10132
  signal.throwIfAborted();
9927
10133
  return {
@@ -9933,7 +10139,11 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, planHistoryRea
9933
10139
  };
9934
10140
  } catch (error) {
9935
10141
  if (signal.aborted) throw error;
9936
- const known = /* @__PURE__ */ new Set(["ChatGPT subscription is not signed in", "ChatGPT sign-in needs to be renewed"]);
10142
+ const known = /* @__PURE__ */ new Set([
10143
+ "ChatGPT subscription is not signed in",
10144
+ "ChatGPT sign-in needs to be renewed",
10145
+ REQUEST_BLOCKED
10146
+ ]);
9937
10147
  return publicError("internal", error instanceof Error && known.has(error.message) ? error.message : "Could not read ChatGPT usage history");
9938
10148
  }
9939
10149
  if (endpoint === "reset-credit/inspect" || endpoint === "reset-credit/prepare" || endpoint === "reset-credit/consume") try {
@@ -9954,6 +10164,7 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, planHistoryRea
9954
10164
  const known = /* @__PURE__ */ new Set([
9955
10165
  "ChatGPT subscription is not signed in",
9956
10166
  "ChatGPT sign-in needs to be renewed",
10167
+ REQUEST_BLOCKED,
9957
10168
  "No quota reset is available",
9958
10169
  "No usable quota reset is available",
9959
10170
  "The available quota reset expires too soon",
@@ -9978,7 +10189,7 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, planHistoryRea
9978
10189
  () => modelCatalog?.clear(),
9979
10190
  () => onAccountChanged?.()
9980
10191
  ];
9981
- if (endpoint === "logout") tasks.push(() => usageReader.clear());
10192
+ if (endpoint === "logout") tasks.push(() => usageReader.clear(), () => accountUsageReader?.clear());
9982
10193
  if (endpoint === "account/remove" && typeof payload?.id === "string") tasks.push(() => usageReader.clearScope(payload.id));
9983
10194
  if ((await Promise.allSettled(tasks.map((task) => Promise.resolve().then(task)))).some((value) => value.status === "rejected")) try {
9984
10195
  onCleanupFailure?.();
@@ -10533,9 +10744,21 @@ function apply(ctx, config = {}) {
10533
10744
  sketchBridge.dispose();
10534
10745
  };
10535
10746
  }, "codex-subscription: native sketch tool");
10747
+ const accountUsageReader = createAccountUsageReader({
10748
+ vault: accountVault,
10749
+ createModels: (credentials) => {
10750
+ const accountModels = createModels({ credentials });
10751
+ accountModels.setProvider(provider);
10752
+ return accountModels;
10753
+ },
10754
+ fetch: (input, init) => network.fetch("quota", input, init),
10755
+ expirySkewMs: OAUTH_EXPIRY_SKEW_MS,
10756
+ activeReader: baseUsageReader
10757
+ });
10536
10758
  const subscriptionHandler = createSubscriptionRpcHandler({
10537
10759
  authHandler: createCodexRpcHandler(coordinator, { openExternal: openCodexAuthUrl }),
10538
10760
  usageReader,
10761
+ accountUsageReader,
10539
10762
  planHistoryReader,
10540
10763
  usageProfileReader,
10541
10764
  resetCreditService,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "3.0.0-beta.7",
3
+ "version": "3.0.0-beta.8",
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",
@@ -77,24 +77,24 @@
77
77
  },
78
78
  "peerDependencies": {
79
79
  "@deepseek-ai/cordis": "4.0.4 || 4.0.5-alpha.1 || 4.0.5-alpha.2",
80
- "@deepseek-ai/dsh-api-remotes": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
81
- "@deepseek-ai/dsh-client-connection": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
82
- "@deepseek-ai/dsh-client-locale": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
83
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
84
- "@deepseek-ai/dsh-client-ui-model-selection": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
85
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
86
- "@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
87
- "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
88
- "@deepseek-ai/dsh-client-ui-tool": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
89
- "@deepseek-ai/dsh-credentials": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
90
- "@deepseek-ai/dsh-llm": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
91
- "@deepseek-ai/dsh-llm-pi-ai": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
92
- "@deepseek-ai/dsh-settings": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
93
- "@deepseek-ai/dsh-subagent-codex": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
94
- "@deepseek-ai/dsh-tools": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
95
- "@deepseek-ai/dsh-web": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3",
80
+ "@deepseek-ai/dsh-api-remotes": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
81
+ "@deepseek-ai/dsh-client-connection": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
82
+ "@deepseek-ai/dsh-client-locale": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
83
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
84
+ "@deepseek-ai/dsh-client-ui-model-selection": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
85
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
86
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
87
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
88
+ "@deepseek-ai/dsh-client-ui-tool": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
89
+ "@deepseek-ai/dsh-credentials": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
90
+ "@deepseek-ai/dsh-llm": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
91
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
92
+ "@deepseek-ai/dsh-settings": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
93
+ "@deepseek-ai/dsh-subagent-codex": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
94
+ "@deepseek-ai/dsh-tools": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
95
+ "@deepseek-ai/dsh-web": "0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5",
96
96
  "@deepseek-ai/schemastery": "3.18.1 || ^3.18.2",
97
- "@earendil-works/pi-ai": "0.85.1 || 0.87.1 || 1.1.0 || 1.2.0",
97
+ "@earendil-works/pi-ai": "0.85.1 || 0.87.1 || 1.1.0 || 1.2.0 || 1.3.0",
98
98
  "react": "^18.2.0",
99
99
  "react-dom": "^18.2.0"
100
100
  },