@liyang8246/dsh-accounts 0.1.4 → 0.1.6

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/client.js CHANGED
@@ -36,6 +36,8 @@
36
36
  empty: "No accounts yet",
37
37
  remove: "Remove",
38
38
  confirmRemove: "Remove this saved login and its local credentials?",
39
+ resetCredit: "Reset",
40
+ confirmResetCredit: "Spend one reset credit to clear the used 5-hour window?",
39
41
  cancel: "Cancel",
40
42
  open: "Open authorization page",
41
43
  code: "Authorization code",
@@ -64,6 +66,8 @@
64
66
  empty: "暂无账号",
65
67
  remove: "删除",
66
68
  confirmRemove: "删除此登录账号及其本地凭据?",
69
+ resetCredit: "重置",
70
+ confirmResetCredit: "消耗 1 次重置额度,清空已用的 5 小时窗口?",
67
71
  cancel: "取消",
68
72
  open: "打开授权页面",
69
73
  code: "授权码",
@@ -134,7 +138,7 @@
134
138
  return isObject(value) && (value.period === "5h" || value.period === "week" || value.period === "month" || value.period === "other") && optionalString(value.label) && optionalString(value.unit) && optionalString(value.resetsAt) && optionalNumber(value.usedPercent) && optionalNumber(value.used) && optionalNumber(value.limit) && (value.exhausted === void 0 || typeof value.exhausted === "boolean");
135
139
  }
136
140
  function isQuota(value) {
137
- return isObject(value) && (value.status === "ok" || value.status === "unavailable" || value.status === "error") && typeof value.fetchedAt === "string" && Array.isArray(value.windows) && value.windows.every(isQuotaWindow) && optionalString(value.reason) && optionalString(value.plan);
141
+ return isObject(value) && (value.status === "ok" || value.status === "unavailable" || value.status === "error") && typeof value.fetchedAt === "string" && Array.isArray(value.windows) && value.windows.every(isQuotaWindow) && optionalString(value.reason) && optionalString(value.plan) && optionalNumber(value.resetCredits);
138
142
  }
139
143
  function isAccount(value) {
140
144
  return isObject(value) && typeof value.id === "string" && isChannelId(value.channel) && typeof value.label === "string" && typeof value.createdAt === "string" && (value.auth === "api-key" || value.auth === "oauth") && typeof value.active === "boolean" && optionalString(value.email) && optionalString(value.orgName) && (value.quota === void 0 || isQuota(value.quota));
@@ -235,6 +239,7 @@
235
239
  const [answer, setAnswer] = React.useState("");
236
240
  const [paste, setPaste] = React.useState(false);
237
241
  const [removing, setRemoving] = React.useState(null);
242
+ const [resetting, setResetting] = React.useState(null);
238
243
  const mounted = React.useRef(true);
239
244
  React.useEffect(() => {
240
245
  mounted.current = true;
@@ -307,6 +312,7 @@
307
312
  const value = isLoginRequest(request) ? await call(request) : await store.update(() => call(request));
308
313
  if ("accounts" in value) {
309
314
  if (mounted.current && request.action === "remove") setRemoving(null);
315
+ if (mounted.current && request.action === "reset_credit") setResetting(null);
310
316
  if (mounted.current && request.action === "add_key") {
311
317
  setApiKey("");
312
318
  setForm(null);
@@ -429,7 +435,13 @@
429
435
  id: account.id
430
436
  });
431
437
  }
432
- }), /* @__PURE__ */ React.createElement("strong", null, account.label)), account.quota?.plan && /* @__PURE__ */ React.createElement("span", { className: "zph-pill" }, account.quota.plan), /* @__PURE__ */ React.createElement("div", { className: "zph-actions" }, button(t("refresh"), {
438
+ }), /* @__PURE__ */ React.createElement("strong", null, account.label)), account.quota?.plan && /* @__PURE__ */ React.createElement("span", { className: "zph-pill" }, account.quota.plan), /* @__PURE__ */ React.createElement("div", { className: "zph-actions" }, typeof account.quota?.resetCredits === "number" && /* @__PURE__ */ React.createElement("button", {
439
+ type: "button",
440
+ disabled: busy || !account.quota.resetCredits,
441
+ "aria-expanded": resetting === account.id,
442
+ "aria-controls": `zph-reset-${account.id}`,
443
+ onClick: () => setResetting(account.id)
444
+ }, `${t("resetCredit")} (${account.quota.resetCredits})`), button(t("refresh"), {
433
445
  action: "refresh_quota",
434
446
  id: account.id
435
447
  }), /* @__PURE__ */ React.createElement("button", {
@@ -438,7 +450,19 @@
438
450
  "aria-expanded": removing === account.id,
439
451
  "aria-controls": `zph-remove-${account.id}`,
440
452
  onClick: () => setRemoving(account.id)
441
- }, t("remove")))), removing === account.id && /* @__PURE__ */ React.createElement("div", {
453
+ }, t("remove")))), resetting === account.id && /* @__PURE__ */ React.createElement("div", {
454
+ id: `zph-reset-${account.id}`,
455
+ className: "zph-row",
456
+ role: "group",
457
+ "aria-label": t("confirmResetCredit")
458
+ }, /* @__PURE__ */ React.createElement("span", null, t("confirmResetCredit")), button(t("resetCredit"), {
459
+ action: "reset_credit",
460
+ id: account.id
461
+ }), /* @__PURE__ */ React.createElement("button", {
462
+ type: "button",
463
+ disabled: busy,
464
+ onClick: () => setResetting(null)
465
+ }, t("cancel"))), removing === account.id && /* @__PURE__ */ React.createElement("div", {
442
466
  id: `zph-remove-${account.id}`,
443
467
  className: "zph-row",
444
468
  role: "group",
package/lib/index.js CHANGED
@@ -6708,6 +6708,16 @@ async function loginOpenCode(interaction) {
6708
6708
  });
6709
6709
  }
6710
6710
  // src/providers/quota.ts
6711
+ const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
6712
+ const CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume";
6713
+ function codexHeaders(account) {
6714
+ if (account.credential.kind !== "oauth") throw new Error("Codex requires OAuth");
6715
+ return {
6716
+ "Authorization": `Bearer ${account.credential.tokens.access}`,
6717
+ "User-Agent": "dsh-zephyr/0.2.0",
6718
+ ...account.credential.tokens.accountId ? { "ChatGPT-Account-Id": account.credential.tokens.accountId } : {}
6719
+ };
6720
+ }
6711
6721
  function percent(value) {
6712
6722
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
6713
6723
  }
@@ -6718,6 +6728,8 @@ function reset(value) {
6718
6728
  function parseOpenAIQuota(input) {
6719
6729
  const body = record(input, "OpenAI usage");
6720
6730
  const rate = record(body.rate_limit, "OpenAI rate limit");
6731
+ const credits = body.rate_limit_reset_credits;
6732
+ const resetCredits = percent(credits && typeof credits === "object" && !Array.isArray(credits) ? credits.available_count : void 0);
6721
6733
  const windows = [];
6722
6734
  for (const key of ["primary_window", "secondary_window"]) {
6723
6735
  if (rate[key] == null) continue;
@@ -6741,6 +6753,7 @@ function parseOpenAIQuota(input) {
6741
6753
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
6742
6754
  windows,
6743
6755
  ...typeof body.plan_type === "string" ? { plan: body.plan_type.slice(0, 80) } : {},
6756
+ ...resetCredits === void 0 ? {} : { resetCredits },
6744
6757
  ...!windows.length ? { reason: "OpenAI did not return supported quota windows" } : {}
6745
6758
  };
6746
6759
  }
@@ -6773,15 +6786,11 @@ function parseOpenCodeQuota(input) {
6773
6786
  }
6774
6787
  async function fetchQuota(account, signal) {
6775
6788
  if (account.channel === "commandcode-go") throw new Error("Use the CommandCode quota adapter");
6789
+ if (account.channel === "openai-codex") return parseOpenAIQuota(await fetchJSON(CODEX_USAGE_URL, { headers: codexHeaders(account) }, signal));
6776
6790
  const headers = {
6777
6791
  "Authorization": `Bearer ${account.credential.kind === "api-key" ? account.credential.key : account.credential.tokens.access}`,
6778
6792
  "User-Agent": "dsh-zephyr/0.2.0"
6779
6793
  };
6780
- if (account.channel === "openai-codex") {
6781
- if (account.credential.kind !== "oauth") throw new Error("Codex requires OAuth");
6782
- if (account.credential.tokens.accountId) headers["ChatGPT-Account-Id"] = account.credential.tokens.accountId;
6783
- return parseOpenAIQuota(await fetchJSON("https://chatgpt.com/backend-api/wham/usage", { headers }, signal));
6784
- }
6785
6794
  if (account.credential.kind === "oauth" && account.credential.tokens.orgId) headers["x-opencode-org-id"] = account.credential.tokens.orgId;
6786
6795
  try {
6787
6796
  return parseOpenCodeQuota(await fetchJSON("https://opencode.ai/zen/go/v1/usage", { headers }, signal));
@@ -6795,6 +6804,21 @@ async function fetchQuota(account, signal) {
6795
6804
  };
6796
6805
  }
6797
6806
  }
6807
+ async function consumeCodexResetCredit(account, signal) {
6808
+ const body = record(await fetchJSON(CODEX_RESET_CREDITS_URL, {
6809
+ method: "POST",
6810
+ headers: {
6811
+ ...codexHeaders(account),
6812
+ "Content-Type": "application/json"
6813
+ },
6814
+ body: JSON.stringify({ redeem_request_id: randomUUID() })
6815
+ }, signal), "Codex reset credit");
6816
+ if (body.code === "reset") return;
6817
+ if (body.code === "no_credit") throw new Error("No reset credit is available for this account");
6818
+ if (body.code === "nothing_to_reset") throw new Error("No used quota window needs a reset");
6819
+ if (body.code === "already_redeemed") throw new Error("This reset request was already redeemed");
6820
+ throw new Error(`Codex reset returned an unknown outcome: ${String(body.code).slice(0, 40)}`);
6821
+ }
6798
6822
  // src/shared/channels.ts
6799
6823
  const channels = [
6800
6824
  {
@@ -6893,6 +6917,7 @@ function readQuotas(value) {
6893
6917
  windows: view.windows.flatMap(readWindow)
6894
6918
  };
6895
6919
  for (const key of ["reason", "plan"]) if (typeof view[key] === "string") quota[key] = view[key];
6920
+ if (typeof view.resetCredits === "number" && Number.isFinite(view.resetCredits) && view.resetCredits >= 0) quota.resetCredits = view.resetCredits;
6896
6921
  return [[id, quota]];
6897
6922
  }));
6898
6923
  }
@@ -7290,6 +7315,13 @@ var ModelAccountsService = class {
7290
7315
  this.fetching.delete(id);
7291
7316
  }
7292
7317
  }
7318
+ async resetCredit(id, signal) {
7319
+ const view = (await this.accounts.list()).find((account) => account.id === id);
7320
+ if (!view) throw new Error("Account not found");
7321
+ if (view.channel !== "openai-codex") throw new Error("Only OpenAI Codex accounts have reset credits");
7322
+ await consumeCodexResetCredit(await this.accounts.resolve(view.channel, signal, id), signal);
7323
+ await this.refreshQuota(id, signal);
7324
+ }
7293
7325
  async dispatch(raw, origin, signal) {
7294
7326
  if (this.controller.signal.aborted) throw new Error("Plugin is unloading");
7295
7327
  const args = record(raw);
@@ -7321,6 +7353,10 @@ var ModelAccountsService = class {
7321
7353
  this.onChange?.();
7322
7354
  break;
7323
7355
  }
7356
+ case "reset_credit":
7357
+ if (origin !== "ui") throw new Error("Resetting quota is user-only; open Settings → Model Accounts");
7358
+ await this.resetCredit(text(args.id, "account id"), signal);
7359
+ break;
7324
7360
  case "refresh_quota": {
7325
7361
  const accounts = await this.accounts.list();
7326
7362
  const ids = args.id === void 0 ? accounts.map((account) => account.id) : [text(args.id, "account id")];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@liyang8246/dsh-accounts",
3
3
  "type": "module",
4
- "version": "0.1.4",
4
+ "version": "0.1.6",
5
5
  "description": "Independent multi-account Codex, OpenCode Go and CommandCode Go plugin for DeepSeek Harness.",
6
6
  "repository": { "type": "git", "url": "https://github.com/liyang8246/dsh-zolin.git", "directory": "packages/dsh-accounts" },
7
7
  "exports": {