dsh-codex-subscription 2.6.9 → 2.7.0
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/README.en.md +5 -5
- package/README.md +5 -5
- package/lib/client.js +115 -20
- package/lib/index.js +229 -27
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -106,7 +106,7 @@ This plugin supports the latest DeepSeek Harness release recorded in its package
|
|
|
106
106
|
2. Paste this package name, with its version, into the **Package name or address** field:
|
|
107
107
|
|
|
108
108
|
```text
|
|
109
|
-
dsh-codex-subscription@2.
|
|
109
|
+
dsh-codex-subscription@2.7.0
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
3. Click **Install** and wait for completion. Follow the page instructions; save your work before restarting if requested.
|
|
@@ -118,7 +118,7 @@ Why the version is included: DSH's plugin page only installs versions that are a
|
|
|
118
118
|
<summary>Terminal installation (with an existing dsh command)</summary>
|
|
119
119
|
|
|
120
120
|
```sh
|
|
121
|
-
dsh plugin --profile web add dsh-codex-subscription@2.
|
|
121
|
+
dsh plugin --profile web add dsh-codex-subscription@2.7.0
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
Follow the restart instructions, then sign in under **Settings → Codex**. Both the Plugins page and the terminal use DSH's installation management.
|
|
@@ -131,7 +131,7 @@ Follow the restart instructions, then sign in under **Settings → Codex**. Both
|
|
|
131
131
|
After signing in and selecting a Codex model in Web, install the same plugin in the Headless profile:
|
|
132
132
|
|
|
133
133
|
```sh
|
|
134
|
-
dsh plugin --profile headless add dsh-codex-subscription@2.
|
|
134
|
+
dsh plugin --profile headless add dsh-codex-subscription@2.7.0
|
|
135
135
|
dsh --profile headless "Reply with only the word: ok"
|
|
136
136
|
```
|
|
137
137
|
|
|
@@ -266,7 +266,7 @@ Find this plugin on the DSH **Plugins** page and use its update or uninstall act
|
|
|
266
266
|
<summary>Terminal commands</summary>
|
|
267
267
|
|
|
268
268
|
```sh
|
|
269
|
-
dsh plugin --profile web update dsh-codex-subscription@2.
|
|
269
|
+
dsh plugin --profile web update dsh-codex-subscription@2.7.0
|
|
270
270
|
```
|
|
271
271
|
|
|
272
272
|
Run only when you want to uninstall:
|
|
@@ -282,7 +282,7 @@ dsh plugin --profile web remove dsh-codex-subscription
|
|
|
282
282
|
<details>
|
|
283
283
|
<summary>DSH 0.2.0-rc.2 reports the plugin as incompatible?</summary>
|
|
284
284
|
|
|
285
|
-
DSH 0.2.0-rc.2 did not exist when any release before 2.2.10 was published, and a published package's compatibility declaration cannot be changed. Install 2.2.10 or later (a bare package name can resolve to an older release during the first 24 hours, so enter `dsh-codex-subscription@2.2.10`) from the **Plugins** page, or run `dsh plugin --profile web update dsh-codex-subscription@2.
|
|
285
|
+
DSH 0.2.0-rc.2 did not exist when any release before 2.2.10 was published, and a published package's compatibility declaration cannot be changed. Install 2.2.10 or later (a bare package name can resolve to an older release during the first 24 hours, so enter `dsh-codex-subscription@2.2.10`) from the **Plugins** page, or run `dsh plugin --profile web update dsh-codex-subscription@2.7.0`, then restart DSH.
|
|
286
286
|
|
|
287
287
|
</details>
|
|
288
288
|
|
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
2. 在 **包名或地址** 输入框中粘贴下面的包名(已带版本号):
|
|
107
107
|
|
|
108
108
|
```text
|
|
109
|
-
dsh-codex-subscription@2.
|
|
109
|
+
dsh-codex-subscription@2.7.0
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
3. 点击 **安装**,等待安装完成;按页面提示操作,需要重启时先保存工作。
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
<summary>终端安装(已能运行 dsh 命令)</summary>
|
|
119
119
|
|
|
120
120
|
```sh
|
|
121
|
-
dsh plugin --profile web add dsh-codex-subscription@2.
|
|
121
|
+
dsh plugin --profile web add dsh-codex-subscription@2.7.0
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
安装完成后按提示重启 DSH,再到 **设置 → Codex 订阅** 登录。插件页面和终端均由 DSH 管理安装。
|
|
@@ -131,7 +131,7 @@ dsh plugin --profile web add dsh-codex-subscription@2.6.9
|
|
|
131
131
|
先在 Web 中完成登录并选择一次 Codex 模型,再把同一个插件安装到 Headless profile:
|
|
132
132
|
|
|
133
133
|
```sh
|
|
134
|
-
dsh plugin --profile headless add dsh-codex-subscription@2.
|
|
134
|
+
dsh plugin --profile headless add dsh-codex-subscription@2.7.0
|
|
135
135
|
dsh --profile headless "只回复:ok"
|
|
136
136
|
```
|
|
137
137
|
|
|
@@ -295,7 +295,7 @@ DSH 实机设置截图。
|
|
|
295
295
|
<summary>终端方式</summary>
|
|
296
296
|
|
|
297
297
|
```sh
|
|
298
|
-
dsh plugin --profile web update dsh-codex-subscription@2.
|
|
298
|
+
dsh plugin --profile web update dsh-codex-subscription@2.7.0
|
|
299
299
|
```
|
|
300
300
|
|
|
301
301
|
仅在需要卸载时运行:
|
|
@@ -311,7 +311,7 @@ dsh plugin --profile web remove dsh-codex-subscription
|
|
|
311
311
|
<details>
|
|
312
312
|
<summary>DSH 0.2.0-rc.2 提示插件不兼容?</summary>
|
|
313
313
|
|
|
314
|
-
2.2.10 之前的所有版本发布时 DSH 0.2.0-rc.2 还不存在,其兼容声明无法事后修改。请安装 2.2.10 或更高版本(只填包名时,发布满 24 小时前可能装到旧版,请直接填 `dsh-codex-subscription@2.2.10`):在 **插件** 页面更新,或运行 `dsh plugin --profile web update dsh-codex-subscription@2.
|
|
314
|
+
2.2.10 之前的所有版本发布时 DSH 0.2.0-rc.2 还不存在,其兼容声明无法事后修改。请安装 2.2.10 或更高版本(只填包名时,发布满 24 小时前可能装到旧版,请直接填 `dsh-codex-subscription@2.2.10`):在 **插件** 页面更新,或运行 `dsh plugin --profile web update dsh-codex-subscription@2.7.0`,然后重启 DSH。
|
|
315
315
|
|
|
316
316
|
</details>
|
|
317
317
|
|
package/lib/client.js
CHANGED
|
@@ -1074,6 +1074,7 @@ window.__ModuleLoader__.load({
|
|
|
1074
1074
|
"account/select",
|
|
1075
1075
|
"account/remove",
|
|
1076
1076
|
"usage",
|
|
1077
|
+
"usage/accounts",
|
|
1077
1078
|
"diagnostics",
|
|
1078
1079
|
"preferences/status",
|
|
1079
1080
|
"preferences/models",
|
|
@@ -6037,7 +6038,13 @@ window.__ModuleLoader__.load({
|
|
|
6037
6038
|
resetNoCredit: "没有可用的额度重置。",
|
|
6038
6039
|
resetAlready: "这次重置请求已处理。",
|
|
6039
6040
|
resetFailed: "无法使用额度重置。",
|
|
6041
|
+
accountQuotaLeft: "{label}剩余 {value}%",
|
|
6042
|
+
accountQuotaResets: "{value} 重置",
|
|
6043
|
+
accountQuotaLoading: "正在读取额度…",
|
|
6044
|
+
accountQuotaError: "暂时读不到这个账号的额度",
|
|
6045
|
+
accountQuotaNone: "没有额度窗口信息",
|
|
6040
6046
|
resetRenewLogin: "登录状态已失效,请重新登录。",
|
|
6047
|
+
resetBlocked: "ChatGPT 的网络防护拦截了这次请求(HTTP 403),登录状态没有问题。请稍后再试,或检查网络、代理或 VPN。",
|
|
6041
6048
|
resetExpired: "本次确认已失效,请重新开始。",
|
|
6042
6049
|
resetInProgress: "额度重置正在处理中。",
|
|
6043
6050
|
resetTooEarly: "请等待冷静期结束后再确认。",
|
|
@@ -6643,7 +6650,13 @@ window.__ModuleLoader__.load({
|
|
|
6643
6650
|
resetNoCredit: "No quota reset is available.",
|
|
6644
6651
|
resetAlready: "This reset request was already processed.",
|
|
6645
6652
|
resetFailed: "Could not use the quota reset.",
|
|
6653
|
+
accountQuotaLeft: "{label} {value}% left",
|
|
6654
|
+
accountQuotaResets: "resets {value}",
|
|
6655
|
+
accountQuotaLoading: "Reading quota…",
|
|
6656
|
+
accountQuotaError: "The quota of this account could not be read right now",
|
|
6657
|
+
accountQuotaNone: "No quota window reported",
|
|
6646
6658
|
resetRenewLogin: "Your sign-in expired. Sign in again.",
|
|
6659
|
+
resetBlocked: "ChatGPT network protection blocked this request (HTTP 403). Your sign-in is fine; try again later or check your network, proxy or VPN.",
|
|
6647
6660
|
resetExpired: "This confirmation expired. Start again.",
|
|
6648
6661
|
resetInProgress: "A quota reset is already in progress.",
|
|
6649
6662
|
resetTooEarly: "Wait for the cooldown before confirming.",
|
|
@@ -6849,6 +6862,9 @@ window.__ModuleLoader__.load({
|
|
|
6849
6862
|
.codexSubscriptionAccounts{display:flex;flex-direction:column;border-top:1px solid var(--dsw-alias-border-l2)}
|
|
6850
6863
|
.codexSubscriptionAccount{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:42px;border-bottom:1px solid var(--dsw-alias-border-l2);font-size:13px}
|
|
6851
6864
|
.codexSubscriptionAccount:last-child{border-bottom:0}
|
|
6865
|
+
.codexSubscriptionAccountMain{display:flex;flex-direction:column;gap:2px;padding:6px 0;min-width:0}
|
|
6866
|
+
.codexSubscriptionAccountQuota{display:flex;flex-wrap:wrap;gap:2px 12px;font-size:12px;color:var(--dsw-alias-text-l2)}
|
|
6867
|
+
.codexSubscriptionAccountQuota[data-state=error]{color:var(--dsw-alias-text-danger,#c0392b)}
|
|
6852
6868
|
.codexSubscriptionAccount[data-active=true] .codexSubscriptionEmail,.codexSubscriptionAccount[data-active=true]>span{font-weight:600}
|
|
6853
6869
|
.codexSubscriptionEmail{max-width:100%;overflow:hidden;padding:2px 4px;border:0;border-radius:5px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}
|
|
6854
6870
|
.codexSubscriptionEmail:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
@@ -9078,7 +9094,9 @@ window.__ModuleLoader__.load({
|
|
|
9078
9094
|
return `${local[0]}•••${local.at(-1)}@${domain}`;
|
|
9079
9095
|
};
|
|
9080
9096
|
const hours = (seconds) => Math.round(seconds / 3600 * 10) / 10;
|
|
9081
|
-
|
|
9097
|
+
/** The language DSH shows its interface in, so dates and numbers match the text around them rather than the OS. */
|
|
9098
|
+
const uiLocale = () => typeof document === "undefined" ? void 0 : document.documentElement.lang || void 0;
|
|
9099
|
+
const percent = (value) => Number(value).toLocaleString(uiLocale(), { maximumFractionDigits: 1 });
|
|
9082
9100
|
const isApproximateWindow = (seconds, expected) => seconds >= expected * .95 && seconds <= expected * 1.05;
|
|
9083
9101
|
const windowLabel = (seconds, t) => {
|
|
9084
9102
|
if (isApproximateWindow(seconds, 18e3)) return t("windowFiveHours");
|
|
@@ -9155,7 +9173,7 @@ window.__ModuleLoader__.load({
|
|
|
9155
9173
|
}
|
|
9156
9174
|
//#endregion
|
|
9157
9175
|
//#region src/version.js
|
|
9158
|
-
const PACKAGE_VERSION = "2.
|
|
9176
|
+
const PACKAGE_VERSION = "2.7.0";
|
|
9159
9177
|
//#endregion
|
|
9160
9178
|
//#region src/client-recovery.js
|
|
9161
9179
|
async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
|
|
@@ -9383,7 +9401,7 @@ window.__ModuleLoader__.load({
|
|
|
9383
9401
|
}
|
|
9384
9402
|
function shortReset(quota, t) {
|
|
9385
9403
|
if (!Number.isSafeInteger(quota.resetsAt)) return t("resetUnknown");
|
|
9386
|
-
const value = (/* @__PURE__ */ new Date(quota.resetsAt * 1e3)).toLocaleString(
|
|
9404
|
+
const value = (/* @__PURE__ */ new Date(quota.resetsAt * 1e3)).toLocaleString(uiLocale(), {
|
|
9387
9405
|
month: "numeric",
|
|
9388
9406
|
day: "numeric",
|
|
9389
9407
|
hour: "2-digit",
|
|
@@ -11152,6 +11170,73 @@ window.__ModuleLoader__.load({
|
|
|
11152
11170
|
}
|
|
11153
11171
|
}
|
|
11154
11172
|
//#endregion
|
|
11173
|
+
//#region src/client-account-quota.jsx
|
|
11174
|
+
const when = (seconds) => (/* @__PURE__ */ new Date(seconds * 1e3)).toLocaleString(uiLocale(), {
|
|
11175
|
+
month: "numeric",
|
|
11176
|
+
day: "numeric",
|
|
11177
|
+
hour: "2-digit",
|
|
11178
|
+
minute: "2-digit"
|
|
11179
|
+
});
|
|
11180
|
+
/** Quota of every signed-in account (read-only; the active account does not change). */
|
|
11181
|
+
function useAccountQuotas(rpc, ids) {
|
|
11182
|
+
const [entries, setEntries] = (0, react.useState)({});
|
|
11183
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
11184
|
+
const request = (0, react.useRef)(0);
|
|
11185
|
+
const key = ids.join(",");
|
|
11186
|
+
const load = (force) => {
|
|
11187
|
+
if (ids.length < 2) {
|
|
11188
|
+
setEntries({});
|
|
11189
|
+
return;
|
|
11190
|
+
}
|
|
11191
|
+
const id = ++request.current;
|
|
11192
|
+
setBusy(true);
|
|
11193
|
+
recoveryCall(rpc, "usage/accounts", { force }).then((value) => {
|
|
11194
|
+
if (request.current === id) setEntries(Object.fromEntries((value?.accounts ?? []).map((entry) => [entry.id, entry])));
|
|
11195
|
+
}).catch(() => {
|
|
11196
|
+
if (request.current === id) setEntries(Object.fromEntries(ids.map((account) => [account, {
|
|
11197
|
+
id: account,
|
|
11198
|
+
error: "Could not read ChatGPT usage"
|
|
11199
|
+
}])));
|
|
11200
|
+
}).finally(() => {
|
|
11201
|
+
if (request.current === id) setBusy(false);
|
|
11202
|
+
});
|
|
11203
|
+
};
|
|
11204
|
+
(0, react.useEffect)(() => {
|
|
11205
|
+
load(false);
|
|
11206
|
+
return () => {
|
|
11207
|
+
request.current += 1;
|
|
11208
|
+
};
|
|
11209
|
+
}, [key]);
|
|
11210
|
+
return {
|
|
11211
|
+
entries,
|
|
11212
|
+
busy,
|
|
11213
|
+
refresh: () => load(true)
|
|
11214
|
+
};
|
|
11215
|
+
}
|
|
11216
|
+
function AccountQuota({ entry, loading, t }) {
|
|
11217
|
+
if (entry === void 0) return loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
11218
|
+
className: "codexSubscriptionAccountQuota",
|
|
11219
|
+
children: t("accountQuotaLoading")
|
|
11220
|
+
}) : null;
|
|
11221
|
+
if (entry.error !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
11222
|
+
className: "codexSubscriptionAccountQuota",
|
|
11223
|
+
"data-state": "error",
|
|
11224
|
+
children: entry.error === "ChatGPT sign-in needs to be renewed" ? t("resetRenewLogin") : t("accountQuotaError")
|
|
11225
|
+
});
|
|
11226
|
+
const windows = entry.usage?.rateLimits?.find((limit) => limit.id === "codex")?.windows ?? [];
|
|
11227
|
+
if (windows.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
11228
|
+
className: "codexSubscriptionAccountQuota",
|
|
11229
|
+
children: t("accountQuotaNone")
|
|
11230
|
+
});
|
|
11231
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", {
|
|
11232
|
+
className: "codexSubscriptionAccountQuota",
|
|
11233
|
+
children: windows.map((window) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [fill(t("accountQuotaLeft"), {
|
|
11234
|
+
label: windowLabel(window.windowSeconds, t),
|
|
11235
|
+
value: percent(window.remainingPercent)
|
|
11236
|
+
}), window.resetsAt === void 0 ? "" : ` · ${fill(t("accountQuotaResets"), { value: when(window.resetsAt) })}`] }, window.windowSeconds))
|
|
11237
|
+
});
|
|
11238
|
+
}
|
|
11239
|
+
//#endregion
|
|
11155
11240
|
//#region src/client-account.jsx
|
|
11156
11241
|
const LOGIN_FAILURES = [
|
|
11157
11242
|
"account-claim",
|
|
@@ -11173,6 +11258,7 @@ window.__ModuleLoader__.load({
|
|
|
11173
11258
|
children: emailVisible ? candidate.email : maskEmail(candidate.email)
|
|
11174
11259
|
});
|
|
11175
11260
|
}
|
|
11261
|
+
const signedInIds = (account) => account?.authenticated === true ? (account.accounts ?? []).map((candidate) => candidate.id).filter((id) => typeof id === "string") : [];
|
|
11176
11262
|
function AccountCard({ rpc, t, account, setAccount, onSignedOut }) {
|
|
11177
11263
|
const [flow, setFlow] = (0, react.useState)();
|
|
11178
11264
|
const flowGeneration = (0, react.useRef)(0);
|
|
@@ -11185,6 +11271,7 @@ window.__ModuleLoader__.load({
|
|
|
11185
11271
|
const [emailVisibilityKey, setEmailVisibilityKey] = (0, react.useState)(accountVisibilityKey);
|
|
11186
11272
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
11187
11273
|
const [error, setError] = (0, react.useState)();
|
|
11274
|
+
const quotas = useAccountQuotas(rpc, signedInIds(account));
|
|
11188
11275
|
const call = (endpoint, payload = {}) => recoveryCall(rpc, endpoint, payload);
|
|
11189
11276
|
(0, react.useEffect)(() => {
|
|
11190
11277
|
if (emailVisibilityKey === accountVisibilityKey) return;
|
|
@@ -11380,12 +11467,19 @@ window.__ModuleLoader__.load({
|
|
|
11380
11467
|
children: accounts.map((candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11381
11468
|
className: "codexSubscriptionAccount",
|
|
11382
11469
|
"data-active": candidate.active,
|
|
11383
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
11384
|
-
|
|
11385
|
-
|
|
11386
|
-
|
|
11387
|
-
|
|
11388
|
-
|
|
11470
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11471
|
+
className: "codexSubscriptionAccountMain",
|
|
11472
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountEmail, {
|
|
11473
|
+
candidate,
|
|
11474
|
+
fallback: candidate.label,
|
|
11475
|
+
t,
|
|
11476
|
+
emailVisible: emailVisibleForAccount,
|
|
11477
|
+
onClick: toggleEmail
|
|
11478
|
+
}), accounts.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountQuota, {
|
|
11479
|
+
entry: quotas.entries[candidate.id],
|
|
11480
|
+
loading: quotas.busy,
|
|
11481
|
+
t
|
|
11482
|
+
}) : null]
|
|
11389
11483
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11390
11484
|
className: "codexSubscriptionActions",
|
|
11391
11485
|
children: [
|
|
@@ -11789,7 +11883,7 @@ window.__ModuleLoader__.load({
|
|
|
11789
11883
|
children: [
|
|
11790
11884
|
t("diagnosticsSnapshotTime"),
|
|
11791
11885
|
" ",
|
|
11792
|
-
new Date(report.generatedAt).toLocaleString()
|
|
11886
|
+
new Date(report.generatedAt).toLocaleString(uiLocale())
|
|
11793
11887
|
]
|
|
11794
11888
|
}) : null,
|
|
11795
11889
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -11807,7 +11901,7 @@ window.__ModuleLoader__.load({
|
|
|
11807
11901
|
" · ",
|
|
11808
11902
|
t(`diagnosticSource_${event.source}`),
|
|
11809
11903
|
" · ",
|
|
11810
|
-
new Date(event.observedAt).toLocaleTimeString()
|
|
11904
|
+
new Date(event.observedAt).toLocaleTimeString(uiLocale())
|
|
11811
11905
|
] }, `${event.source}:${event.action}`)) }) : null
|
|
11812
11906
|
] }, item.code)) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("diagnosticsNoRecordedFailure") })
|
|
11813
11907
|
}),
|
|
@@ -11825,7 +11919,7 @@ window.__ModuleLoader__.load({
|
|
|
11825
11919
|
] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: (report.inspection?.capabilities ?? []).map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
11826
11920
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: t(`diagnosticCapability_${item.id}`) }),
|
|
11827
11921
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [t(`diagnosticReason_${item.reason}`), item.blockedBy ? ` · ${t(`diagnosticCapability_${item.blockedBy}`)}` : ""] }),
|
|
11828
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [item.latest ? `${item.latest.action} · ${t(`diagnosticSource_${item.latest.source}`)} · ${t(`diagnosticOutcome_${item.latest.status}`)} · ${new Date(item.latest.observedAt).toLocaleTimeString()}` : t("diagnosticsUnverified"), item.unresolved?.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("diagnosticsUnresolved") }) : null] })
|
|
11922
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [item.latest ? `${item.latest.action} · ${t(`diagnosticSource_${item.latest.source}`)} · ${t(`diagnosticOutcome_${item.latest.status}`)} · ${new Date(item.latest.observedAt).toLocaleTimeString(uiLocale())}` : t("diagnosticsUnverified"), item.unresolved?.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("diagnosticsUnresolved") }) : null] })
|
|
11829
11923
|
] }, item.id)) })]
|
|
11830
11924
|
}),
|
|
11831
11925
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -11972,8 +12066,8 @@ window.__ModuleLoader__.load({
|
|
|
11972
12066
|
}
|
|
11973
12067
|
//#endregion
|
|
11974
12068
|
//#region src/client-usage.jsx
|
|
11975
|
-
const count = (value) => Number(value).toLocaleString();
|
|
11976
|
-
const creditAmount = (value) => Number.isFinite(Number(value)) && String(value).trim() !== "" ? Number(value).toLocaleString(
|
|
12069
|
+
const count = (value) => Number(value).toLocaleString(uiLocale());
|
|
12070
|
+
const creditAmount = (value) => Number.isFinite(Number(value)) && String(value).trim() !== "" ? Number(value).toLocaleString(uiLocale(), { maximumFractionDigits: 2 }) : value;
|
|
11977
12071
|
function CreditExpiry({ credits, t }) {
|
|
11978
12072
|
const date = validDate(credits.expiresAt);
|
|
11979
12073
|
if (date === void 0 || credits.unlimited) return null;
|
|
@@ -11984,7 +12078,7 @@ window.__ModuleLoader__.load({
|
|
|
11984
12078
|
"data-soon": status.soon,
|
|
11985
12079
|
role: "status",
|
|
11986
12080
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [fill(t(status.daysLeft === 0 ? "creditsExpiredToday" : "creditsExpires"), {
|
|
11987
|
-
date: date.toLocaleDateString(),
|
|
12081
|
+
date: date.toLocaleDateString(uiLocale()),
|
|
11988
12082
|
days: status.daysLeft
|
|
11989
12083
|
}), estimate ? ` ${t("creditsExpiryEstimate")}` : ""] })
|
|
11990
12084
|
});
|
|
@@ -11992,7 +12086,7 @@ window.__ModuleLoader__.load({
|
|
|
11992
12086
|
function ResetTime({ resetsAt, t }) {
|
|
11993
12087
|
const date = Number.isSafeInteger(resetsAt) ? validDate(resetsAt * 1e3) : void 0;
|
|
11994
12088
|
if (date === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("resetUnknown") });
|
|
11995
|
-
const value = date.toLocaleString(
|
|
12089
|
+
const value = date.toLocaleString(uiLocale(), {
|
|
11996
12090
|
month: "numeric",
|
|
11997
12091
|
day: "numeric",
|
|
11998
12092
|
hour: "2-digit",
|
|
@@ -12000,7 +12094,7 @@ window.__ModuleLoader__.load({
|
|
|
12000
12094
|
});
|
|
12001
12095
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
|
|
12002
12096
|
dateTime: date.toISOString(),
|
|
12003
|
-
title: date.toLocaleString(),
|
|
12097
|
+
title: date.toLocaleString(uiLocale()),
|
|
12004
12098
|
children: fill(t("resets"), { value })
|
|
12005
12099
|
});
|
|
12006
12100
|
}
|
|
@@ -12010,8 +12104,8 @@ window.__ModuleLoader__.load({
|
|
|
12010
12104
|
className: "codexSubscriptionResetExpiry",
|
|
12011
12105
|
children: date === void 0 ? t("resetCreditExpiryUnknown") : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
|
|
12012
12106
|
dateTime: date.toISOString(),
|
|
12013
|
-
title: date.toLocaleString(),
|
|
12014
|
-
children: fill(t("resetCreditExpires"), { value: date.toLocaleDateString(
|
|
12107
|
+
title: date.toLocaleString(uiLocale()),
|
|
12108
|
+
children: fill(t("resetCreditExpires"), { value: date.toLocaleDateString(uiLocale(), {
|
|
12015
12109
|
month: "long",
|
|
12016
12110
|
day: "numeric"
|
|
12017
12111
|
}) })
|
|
@@ -12203,6 +12297,7 @@ window.__ModuleLoader__.load({
|
|
|
12203
12297
|
return t((/* @__PURE__ */ new Map([
|
|
12204
12298
|
["ChatGPT subscription is not signed in", "resetRenewLogin"],
|
|
12205
12299
|
["ChatGPT sign-in needs to be renewed", "resetRenewLogin"],
|
|
12300
|
+
["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", "resetBlocked"],
|
|
12206
12301
|
["No quota reset is available", "resetNoCredit"],
|
|
12207
12302
|
["No usable quota reset is available", "resetNoCredit"],
|
|
12208
12303
|
["The available quota reset expires too soon", "resetExpired"],
|
|
@@ -12265,7 +12360,7 @@ window.__ModuleLoader__.load({
|
|
|
12265
12360
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("usage") }), fetchedAt === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
|
|
12266
12361
|
className: "codexSubscriptionFreshness",
|
|
12267
12362
|
dateTime: fetchedAt.toISOString(),
|
|
12268
|
-
children: fill(t("usageUpdated"), { value: fetchedAt.toLocaleString() })
|
|
12363
|
+
children: fill(t("usageUpdated"), { value: fetchedAt.toLocaleString(uiLocale()) })
|
|
12269
12364
|
})]
|
|
12270
12365
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
12271
12366
|
className: "codexSubscriptionRefresh",
|
package/lib/index.js
CHANGED
|
@@ -1135,6 +1135,7 @@ const RPC_ENDPOINTS = Object.freeze([
|
|
|
1135
1135
|
"account/select",
|
|
1136
1136
|
"account/remove",
|
|
1137
1137
|
"usage",
|
|
1138
|
+
"usage/accounts",
|
|
1138
1139
|
"diagnostics",
|
|
1139
1140
|
"preferences/status",
|
|
1140
1141
|
"preferences/models",
|
|
@@ -2087,9 +2088,36 @@ function inheritedOriginalImageRef(session, assetId) {
|
|
|
2087
2088
|
}
|
|
2088
2089
|
//#endregion
|
|
2089
2090
|
//#region src/version.js
|
|
2090
|
-
const PACKAGE_VERSION = "2.
|
|
2091
|
+
const PACKAGE_VERSION = "2.7.0";
|
|
2091
2092
|
const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
|
|
2092
2093
|
//#endregion
|
|
2094
|
+
//#region src/http-failure.js
|
|
2095
|
+
const SIGN_IN_RENEW = "ChatGPT sign-in needs to be renewed";
|
|
2096
|
+
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";
|
|
2097
|
+
const CHALLENGE_MARKERS = /just a moment|cf-chl|_cf_chl_opt|attention required|enable javascript and cookies|challenge-platform/iu;
|
|
2098
|
+
/** Whether error text is a Cloudflare / network-protection page (pi-ai keeps only the body of a failed request, not its status). */
|
|
2099
|
+
const isChallengePage = (text) => typeof text === "string" && /^\s*<(?:!doctype|html)/iu.test(text) && CHALLENGE_MARKERS.test(text);
|
|
2100
|
+
/** Whether a 403 response is a Cloudflare / network-protection page rather than an answer from the ChatGPT backend. */
|
|
2101
|
+
async function isBlockedByNetwork(response) {
|
|
2102
|
+
if (response?.status !== 403) return false;
|
|
2103
|
+
try {
|
|
2104
|
+
const header = (name) => String(response.headers?.get?.(name) ?? "");
|
|
2105
|
+
if (/challenge/iu.test(header("cf-mitigated"))) return true;
|
|
2106
|
+
if (!/html/iu.test(header("content-type"))) return false;
|
|
2107
|
+
if (/cloudflare/iu.test(header("server"))) return true;
|
|
2108
|
+
const text = typeof response.clone === "function" ? (await response.clone().text()).slice(0, 8192) : "";
|
|
2109
|
+
return CHALLENGE_MARKERS.test(text);
|
|
2110
|
+
} catch {
|
|
2111
|
+
return false;
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
/** The error message for a 401 or 403 answer, or undefined for any other status. */
|
|
2115
|
+
async function authFailureMessage(response) {
|
|
2116
|
+
if (response?.status === 401) return SIGN_IN_RENEW;
|
|
2117
|
+
if (response?.status !== 403) return void 0;
|
|
2118
|
+
return await isBlockedByNetwork(response) ? REQUEST_BLOCKED : SIGN_IN_RENEW;
|
|
2119
|
+
}
|
|
2120
|
+
//#endregion
|
|
2093
2121
|
//#region src/codex-images.js
|
|
2094
2122
|
const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
|
|
2095
2123
|
const CODEX_IMAGE_GENERATION_URL = "https://chatgpt.com/backend-api/codex/images/generations";
|
|
@@ -2592,7 +2620,8 @@ function createCodexImageTool(options) {
|
|
|
2592
2620
|
throw new Error(`Codex image ${editing ? "edit" : "generation"} request failed${requestFailureReason(error)}`, { cause: error });
|
|
2593
2621
|
}
|
|
2594
2622
|
if (!response.ok) {
|
|
2595
|
-
|
|
2623
|
+
const authFailure = await authFailureMessage(response);
|
|
2624
|
+
if (authFailure !== void 0) throw new Error(authFailure);
|
|
2596
2625
|
const requestId = supportId(response);
|
|
2597
2626
|
if (response.status === 429) throw new Error(`${await limitMessage(response)}${requestId}`);
|
|
2598
2627
|
throw new Error(`Codex image ${editing ? "edit" : "generation"} failed (HTTP ${response.status})${requestId}`);
|
|
@@ -3000,6 +3029,42 @@ var DshOAuthAccountVault = class {
|
|
|
3000
3029
|
return clone$1(payload?.accounts.find((account) => account.id === payload.activeId)?.credential);
|
|
3001
3030
|
});
|
|
3002
3031
|
}
|
|
3032
|
+
/** One account's credential, whether or not it is the active one. */
|
|
3033
|
+
readAccount(id) {
|
|
3034
|
+
return this.#enqueue(async () => {
|
|
3035
|
+
const payload = await this.#ensurePayload();
|
|
3036
|
+
return clone$1(payload?.accounts.find((account) => account.id === id)?.credential);
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
/** Update one account's credential (a token refresh) without changing which account is active. */
|
|
3040
|
+
modifyAccount(id, update) {
|
|
3041
|
+
return this.#enqueue(async () => {
|
|
3042
|
+
let result;
|
|
3043
|
+
await this.#modifyPayload(async (current) => {
|
|
3044
|
+
const index = current.accounts.findIndex((account) => account.id === id);
|
|
3045
|
+
if (index < 0) throw new Error("Unknown Codex account");
|
|
3046
|
+
const previous = clone$1(current.accounts[index].credential);
|
|
3047
|
+
const next = await update(previous);
|
|
3048
|
+
if (next === void 0) {
|
|
3049
|
+
result = previous;
|
|
3050
|
+
return current;
|
|
3051
|
+
}
|
|
3052
|
+
const credential = sanitizeOAuthCredential(next);
|
|
3053
|
+
if (credential.email === void 0 && previous.email !== void 0) credential.email = previous.email;
|
|
3054
|
+
const accounts = [...current.accounts];
|
|
3055
|
+
accounts[index] = {
|
|
3056
|
+
...accounts[index],
|
|
3057
|
+
credential
|
|
3058
|
+
};
|
|
3059
|
+
result = clone$1(credential);
|
|
3060
|
+
return {
|
|
3061
|
+
...current,
|
|
3062
|
+
accounts
|
|
3063
|
+
};
|
|
3064
|
+
});
|
|
3065
|
+
return result;
|
|
3066
|
+
});
|
|
3067
|
+
}
|
|
3003
3068
|
activeId() {
|
|
3004
3069
|
return this.#enqueue(async () => (await this.#ensurePayload())?.activeId);
|
|
3005
3070
|
}
|
|
@@ -3099,11 +3164,11 @@ var DshOAuthAccountVault = class {
|
|
|
3099
3164
|
};
|
|
3100
3165
|
//#endregion
|
|
3101
3166
|
//#region src/credential-store.js
|
|
3102
|
-
const PROVIDER$
|
|
3167
|
+
const PROVIDER$2 = "openai-codex";
|
|
3103
3168
|
const abortIfNeeded = (options) => options?.signal?.throwIfAborted();
|
|
3104
3169
|
const clone = (value) => value === void 0 ? void 0 : structuredClone(value);
|
|
3105
|
-
function assertProvider(providerId) {
|
|
3106
|
-
if (providerId !== PROVIDER$
|
|
3170
|
+
function assertProvider$1(providerId) {
|
|
3171
|
+
if (providerId !== PROVIDER$2) throw new Error(`Codex credential store does not own provider ${JSON.stringify(providerId)}`);
|
|
3107
3172
|
}
|
|
3108
3173
|
function assertOAuthCredential(value) {
|
|
3109
3174
|
if (value === void 0) return void 0;
|
|
@@ -3136,7 +3201,7 @@ var DshOAuthCredentialStore = class {
|
|
|
3136
3201
|
this.vault = options.vault;
|
|
3137
3202
|
}
|
|
3138
3203
|
#enqueue(providerId, operation, options) {
|
|
3139
|
-
assertProvider(providerId);
|
|
3204
|
+
assertProvider$1(providerId);
|
|
3140
3205
|
const current = (this.#chains.get(providerId) ?? Promise.resolve()).catch(() => void 0).then(async () => {
|
|
3141
3206
|
abortIfNeeded(options);
|
|
3142
3207
|
return operation();
|
|
@@ -3149,7 +3214,7 @@ var DshOAuthCredentialStore = class {
|
|
|
3149
3214
|
return current;
|
|
3150
3215
|
}
|
|
3151
3216
|
async #read(providerId, options) {
|
|
3152
|
-
assertProvider(providerId);
|
|
3217
|
+
assertProvider$1(providerId);
|
|
3153
3218
|
abortIfNeeded(options);
|
|
3154
3219
|
if (this.vault !== void 0) {
|
|
3155
3220
|
const current = await this.vault.readActive();
|
|
@@ -3182,8 +3247,8 @@ var DshOAuthCredentialStore = class {
|
|
|
3182
3247
|
}
|
|
3183
3248
|
async list(options) {
|
|
3184
3249
|
abortIfNeeded(options);
|
|
3185
|
-
return await this.read(PROVIDER$
|
|
3186
|
-
providerId: PROVIDER$
|
|
3250
|
+
return await this.read(PROVIDER$2, options) === void 0 ? [] : [{
|
|
3251
|
+
providerId: PROVIDER$2,
|
|
3187
3252
|
type: "oauth"
|
|
3188
3253
|
}];
|
|
3189
3254
|
}
|
|
@@ -3233,16 +3298,16 @@ function createCodexAuthService(models, store, options = {}) {
|
|
|
3233
3298
|
const createPendingStore = options.createPendingStore ?? (() => new PendingOAuthCredentialStore());
|
|
3234
3299
|
return Object.freeze({
|
|
3235
3300
|
async status(options) {
|
|
3236
|
-
const current = await store.read(PROVIDER$
|
|
3301
|
+
const current = await store.read(PROVIDER$2, options);
|
|
3237
3302
|
const accounts = await accountVault?.list();
|
|
3238
3303
|
if (current === void 0) return {
|
|
3239
3304
|
authenticated: false,
|
|
3240
|
-
provider: PROVIDER$
|
|
3305
|
+
provider: PROVIDER$2,
|
|
3241
3306
|
...accounts === void 0 ? {} : { accounts }
|
|
3242
3307
|
};
|
|
3243
3308
|
return {
|
|
3244
3309
|
authenticated: true,
|
|
3245
|
-
provider: PROVIDER$
|
|
3310
|
+
provider: PROVIDER$2,
|
|
3246
3311
|
type: "oauth",
|
|
3247
3312
|
expiresAt: current.expires,
|
|
3248
3313
|
...accounts === void 0 ? {} : { accounts }
|
|
@@ -3253,13 +3318,13 @@ function createCodexAuthService(models, store, options = {}) {
|
|
|
3253
3318
|
if (accountVault === void 0 || createLoginModels === void 0) throw new Error("Codex multi-account is unavailable");
|
|
3254
3319
|
return runLogin(async () => {
|
|
3255
3320
|
const pending = createPendingStore();
|
|
3256
|
-
await createLoginModels(pending).login(PROVIDER$
|
|
3321
|
+
await createLoginModels(pending).login(PROVIDER$2, "oauth", interaction);
|
|
3257
3322
|
const credential = pending.credential();
|
|
3258
3323
|
if (credential === void 0) throw new Error("Codex login did not return credentials");
|
|
3259
3324
|
await accountVault.add(input.label, credential);
|
|
3260
3325
|
});
|
|
3261
3326
|
}
|
|
3262
|
-
return runLogin(() => models.login(PROVIDER$
|
|
3327
|
+
return runLogin(() => models.login(PROVIDER$2, "oauth", interaction));
|
|
3263
3328
|
},
|
|
3264
3329
|
async select(id) {
|
|
3265
3330
|
if (accountVault === void 0) throw new Error("Codex multi-account is unavailable");
|
|
@@ -3272,7 +3337,7 @@ function createCodexAuthService(models, store, options = {}) {
|
|
|
3272
3337
|
return this.status();
|
|
3273
3338
|
},
|
|
3274
3339
|
logout(options) {
|
|
3275
|
-
return models.logout(PROVIDER$
|
|
3340
|
+
return models.logout(PROVIDER$2, options);
|
|
3276
3341
|
}
|
|
3277
3342
|
});
|
|
3278
3343
|
}
|
|
@@ -3808,6 +3873,13 @@ function normalizeTransportEvent(event, signal) {
|
|
|
3808
3873
|
if (signal?.aborted || event?.type !== "error" || event.reason !== "error") return event;
|
|
3809
3874
|
const message = event.error?.errorMessage;
|
|
3810
3875
|
if (typeof message !== "string") return event;
|
|
3876
|
+
if (isChallengePage(message)) return {
|
|
3877
|
+
...event,
|
|
3878
|
+
error: {
|
|
3879
|
+
...event.error,
|
|
3880
|
+
errorMessage: REQUEST_BLOCKED
|
|
3881
|
+
}
|
|
3882
|
+
};
|
|
3811
3883
|
if (!TRANSIENT_WEBSOCKET_CLOSE.test(message) && !GATEWAY_REPLAY_BUFFER_EXCEEDED.test(message)) return event;
|
|
3812
3884
|
return {
|
|
3813
3885
|
...event,
|
|
@@ -4532,7 +4604,10 @@ function createCodexSearchProvider(options) {
|
|
|
4532
4604
|
if (signal?.aborted || error?.name === "AbortError") throw new WebError("Codex search aborted", "WEB_ABORTED", { cause: error });
|
|
4533
4605
|
throw new WebError("Codex search request failed", "WEB_PROVIDER_ERROR", { cause: error });
|
|
4534
4606
|
}
|
|
4535
|
-
if (!response.ok)
|
|
4607
|
+
if (!response.ok) {
|
|
4608
|
+
const authFailure = await authFailureMessage(response);
|
|
4609
|
+
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");
|
|
4610
|
+
}
|
|
4536
4611
|
let value;
|
|
4537
4612
|
try {
|
|
4538
4613
|
value = await response.json();
|
|
@@ -5531,7 +5606,7 @@ function createCodexUsageReader(options) {
|
|
|
5531
5606
|
signal: requestSignal$1(signal, timeoutMs)
|
|
5532
5607
|
});
|
|
5533
5608
|
if (!response.ok) {
|
|
5534
|
-
const error =
|
|
5609
|
+
const error = new Error(await authFailureMessage(response) ?? `ChatGPT usage request failed (HTTP ${response.status})`);
|
|
5535
5610
|
Object.defineProperty(error, "retryAfterMs", { value: retryAfterMs(response, now, maxRetryAfterMs) });
|
|
5536
5611
|
throw error;
|
|
5537
5612
|
}
|
|
@@ -5582,6 +5657,102 @@ function createCodexUsageReader(options) {
|
|
|
5582
5657
|
});
|
|
5583
5658
|
}
|
|
5584
5659
|
//#endregion
|
|
5660
|
+
//#region src/account-usage.js
|
|
5661
|
+
const PROVIDER$1 = "openai-codex";
|
|
5662
|
+
const MAX_PARALLEL = 4;
|
|
5663
|
+
const assertProvider = (providerId) => {
|
|
5664
|
+
if (providerId !== PROVIDER$1) throw new Error("Codex account usage received an unknown provider");
|
|
5665
|
+
};
|
|
5666
|
+
/** pi-ai's OAuth credential store, bound to one account of the vault. */
|
|
5667
|
+
function accountCredentialStore(vault, id, expirySkewMs = 0) {
|
|
5668
|
+
const shift = (credential) => credential === void 0 || expirySkewMs === 0 ? credential : {
|
|
5669
|
+
...credential,
|
|
5670
|
+
expires: credential.expires - expirySkewMs
|
|
5671
|
+
};
|
|
5672
|
+
return {
|
|
5673
|
+
async read(providerId) {
|
|
5674
|
+
assertProvider(providerId);
|
|
5675
|
+
return shift(await vault.readAccount(id));
|
|
5676
|
+
},
|
|
5677
|
+
async list() {
|
|
5678
|
+
return await vault.readAccount(id) === void 0 ? [] : [{
|
|
5679
|
+
providerId: PROVIDER$1,
|
|
5680
|
+
type: "oauth"
|
|
5681
|
+
}];
|
|
5682
|
+
},
|
|
5683
|
+
async modify(providerId, update) {
|
|
5684
|
+
assertProvider(providerId);
|
|
5685
|
+
return vault.modifyAccount(id, async (current) => update(shift(current)));
|
|
5686
|
+
},
|
|
5687
|
+
async delete() {
|
|
5688
|
+
throw new Error("An account usage read cannot remove a sign-in");
|
|
5689
|
+
}
|
|
5690
|
+
};
|
|
5691
|
+
}
|
|
5692
|
+
/**
|
|
5693
|
+
* @param {object} options
|
|
5694
|
+
* @param {{ list(): Promise<object[]>, readAccount(id: string): Promise<object|undefined>, modifyAccount: Function }} options.vault
|
|
5695
|
+
* @param {(credentials: object) => object} options.createModels builds a pi-ai Models over a credential store, with the Codex provider set
|
|
5696
|
+
* @param {typeof fetch} options.fetch
|
|
5697
|
+
*/
|
|
5698
|
+
function createAccountUsageReader({ vault, createModels, fetch, expirySkewMs = 0, now = Date.now, activeReader }) {
|
|
5699
|
+
const readers = /* @__PURE__ */ new Map();
|
|
5700
|
+
const readerFor = (id) => {
|
|
5701
|
+
let reader = readers.get(id);
|
|
5702
|
+
if (reader === void 0) {
|
|
5703
|
+
const models = createModels(accountCredentialStore(vault, id, expirySkewMs));
|
|
5704
|
+
reader = createCodexUsageReader({
|
|
5705
|
+
getAuth: () => models.getAuth(PROVIDER$1),
|
|
5706
|
+
readCredential: () => vault.readAccount(id).then((credential) => credential),
|
|
5707
|
+
fetch,
|
|
5708
|
+
now
|
|
5709
|
+
});
|
|
5710
|
+
readers.set(id, reader);
|
|
5711
|
+
}
|
|
5712
|
+
return reader;
|
|
5713
|
+
};
|
|
5714
|
+
const summarize = (error, signal) => {
|
|
5715
|
+
if (signal?.aborted) throw error;
|
|
5716
|
+
const message = error instanceof Error ? error.message : "";
|
|
5717
|
+
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";
|
|
5718
|
+
};
|
|
5719
|
+
return Object.freeze({
|
|
5720
|
+
/** One entry per account: its usage reading, or the reason it could not be read. */
|
|
5721
|
+
async readAll({ force = false, signal } = {}) {
|
|
5722
|
+
const accounts = await vault.list();
|
|
5723
|
+
for (const id of [...readers.keys()]) if (!accounts.some((account) => account.id === id)) readers.delete(id);
|
|
5724
|
+
const entries = new Array(accounts.length);
|
|
5725
|
+
let next = 0;
|
|
5726
|
+
const worker = async () => {
|
|
5727
|
+
while (next < accounts.length) {
|
|
5728
|
+
const index = next++;
|
|
5729
|
+
const account = accounts[index];
|
|
5730
|
+
try {
|
|
5731
|
+
const reader = account.active && activeReader !== void 0 ? activeReader : readerFor(account.id);
|
|
5732
|
+
entries[index] = {
|
|
5733
|
+
id: account.id,
|
|
5734
|
+
usage: await reader.read({
|
|
5735
|
+
force,
|
|
5736
|
+
signal
|
|
5737
|
+
})
|
|
5738
|
+
};
|
|
5739
|
+
} catch (error) {
|
|
5740
|
+
entries[index] = {
|
|
5741
|
+
id: account.id,
|
|
5742
|
+
error: summarize(error, signal)
|
|
5743
|
+
};
|
|
5744
|
+
}
|
|
5745
|
+
}
|
|
5746
|
+
};
|
|
5747
|
+
await Promise.all(Array.from({ length: Math.min(MAX_PARALLEL, accounts.length) }, worker));
|
|
5748
|
+
return entries;
|
|
5749
|
+
},
|
|
5750
|
+
clear() {
|
|
5751
|
+
readers.clear();
|
|
5752
|
+
}
|
|
5753
|
+
});
|
|
5754
|
+
}
|
|
5755
|
+
//#endregion
|
|
5585
5756
|
//#region src/quota-retry.js
|
|
5586
5757
|
const DEFAULT_MAX_WAIT_MS = 360 * 60 * 1e3;
|
|
5587
5758
|
const DEFAULT_RESET_MARGIN_MS = 1e4;
|
|
@@ -6302,7 +6473,7 @@ function createCodexResetCreditService(options) {
|
|
|
6302
6473
|
},
|
|
6303
6474
|
signal: requestSignal(signal, timeoutMs)
|
|
6304
6475
|
});
|
|
6305
|
-
if (!response.ok) throw new Error(
|
|
6476
|
+
if (!response.ok) throw new Error(await authFailureMessage(response) ?? `ChatGPT quota reset request failed (HTTP ${response.status})`);
|
|
6306
6477
|
let raw;
|
|
6307
6478
|
try {
|
|
6308
6479
|
raw = await response.json();
|
|
@@ -6436,7 +6607,7 @@ function createCodexResetCreditService(options) {
|
|
|
6436
6607
|
throw new Error(UNCERTAIN_RESET_RESULT);
|
|
6437
6608
|
}
|
|
6438
6609
|
if (response.status !== 401 && response.status !== 403) retryable = false;
|
|
6439
|
-
throw new Error(
|
|
6610
|
+
throw new Error(await authFailureMessage(response) ?? `ChatGPT quota reset request failed (HTTP ${response.status})`);
|
|
6440
6611
|
}
|
|
6441
6612
|
let raw;
|
|
6442
6613
|
try {
|
|
@@ -6478,7 +6649,7 @@ const publicError = (code, message) => ({
|
|
|
6478
6649
|
details: { issues: [] }
|
|
6479
6650
|
}
|
|
6480
6651
|
});
|
|
6481
|
-
function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditService, preferences, defaultModel, runtimeManagement, diagnosticsReader, modelCatalog, originalImages, resolveInheritedOriginal, closeConnections, onAccountChanged, onCleanupFailure }) {
|
|
6652
|
+
function createSubscriptionRpcHandler({ authHandler, usageReader, accountUsageReader, resetCreditService, preferences, defaultModel, runtimeManagement, diagnosticsReader, modelCatalog, originalImages, resolveInheritedOriginal, closeConnections, onAccountChanged, onCleanupFailure }) {
|
|
6482
6653
|
return async (endpoint, payload, signal) => {
|
|
6483
6654
|
if (endpoint === "default-model/status" || endpoint === "default-model/select") try {
|
|
6484
6655
|
signal.throwIfAborted();
|
|
@@ -6625,10 +6796,27 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
6625
6796
|
};
|
|
6626
6797
|
} catch (error) {
|
|
6627
6798
|
if (signal.aborted) throw error;
|
|
6628
|
-
const known = /* @__PURE__ */ new Set([
|
|
6799
|
+
const known = /* @__PURE__ */ new Set([
|
|
6800
|
+
"ChatGPT subscription is not signed in",
|
|
6801
|
+
"ChatGPT sign-in needs to be renewed",
|
|
6802
|
+
REQUEST_BLOCKED
|
|
6803
|
+
]);
|
|
6629
6804
|
const message = error instanceof Error && known.has(error.message) ? error.message : "Could not read ChatGPT usage";
|
|
6630
6805
|
return publicError("internal", message);
|
|
6631
6806
|
}
|
|
6807
|
+
if (endpoint === "usage/accounts") try {
|
|
6808
|
+
signal.throwIfAborted();
|
|
6809
|
+
return {
|
|
6810
|
+
ok: true,
|
|
6811
|
+
value: { accounts: await accountUsageReader.readAll({
|
|
6812
|
+
force: payload?.force === true,
|
|
6813
|
+
signal
|
|
6814
|
+
}) }
|
|
6815
|
+
};
|
|
6816
|
+
} catch (error) {
|
|
6817
|
+
if (signal.aborted) throw error;
|
|
6818
|
+
return publicError("internal", "Could not read the usage of the signed-in accounts");
|
|
6819
|
+
}
|
|
6632
6820
|
if (endpoint === "reset-credit/inspect" || endpoint === "reset-credit/prepare" || endpoint === "reset-credit/consume") try {
|
|
6633
6821
|
signal.throwIfAborted();
|
|
6634
6822
|
return {
|
|
@@ -6647,6 +6835,7 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
6647
6835
|
const known = /* @__PURE__ */ new Set([
|
|
6648
6836
|
"ChatGPT subscription is not signed in",
|
|
6649
6837
|
"ChatGPT sign-in needs to be renewed",
|
|
6838
|
+
REQUEST_BLOCKED,
|
|
6650
6839
|
"No quota reset is available",
|
|
6651
6840
|
"No usable quota reset is available",
|
|
6652
6841
|
"The available quota reset expires too soon",
|
|
@@ -6669,7 +6858,7 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
6669
6858
|
() => modelCatalog?.clear(),
|
|
6670
6859
|
() => onAccountChanged?.()
|
|
6671
6860
|
];
|
|
6672
|
-
if (endpoint === "logout") tasks.push(() => usageReader.clear());
|
|
6861
|
+
if (endpoint === "logout") tasks.push(() => usageReader.clear(), () => accountUsageReader?.clear());
|
|
6673
6862
|
if (endpoint === "account/remove" && typeof payload?.id === "string") tasks.push(() => usageReader.clearScope(payload.id));
|
|
6674
6863
|
if ((await Promise.allSettled(tasks.map((task) => Promise.resolve().then(task)))).some((value) => value.status === "rejected")) try {
|
|
6675
6864
|
onCleanupFailure?.();
|
|
@@ -7030,12 +7219,13 @@ function apply(ctx, config = {}) {
|
|
|
7030
7219
|
}
|
|
7031
7220
|
});
|
|
7032
7221
|
const coordinator = new CodexLoginCoordinator(auth);
|
|
7222
|
+
const baseUsageReader = createCodexUsageReader({
|
|
7223
|
+
getAuth: resolveAuth,
|
|
7224
|
+
readCredential: (options) => store.read(PROVIDER, options),
|
|
7225
|
+
fetch: (input, init) => network.fetch("quota", input, init)
|
|
7226
|
+
});
|
|
7033
7227
|
const usageReader = createQuotaForecastReader({
|
|
7034
|
-
reader:
|
|
7035
|
-
getAuth: resolveAuth,
|
|
7036
|
-
readCredential: (options) => store.read(PROVIDER, options),
|
|
7037
|
-
fetch: (input, init) => network.fetch("quota", input, init)
|
|
7038
|
-
}),
|
|
7228
|
+
reader: baseUsageReader,
|
|
7039
7229
|
enabled: () => normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]) === QUICK_QUOTA_MODE_FORECAST,
|
|
7040
7230
|
scope: async () => await accountVault?.activeId() ?? "legacy",
|
|
7041
7231
|
stateStore: new QuotaForecastStateStore({ filename: dshHomePath("state", "codex-subscription", "quota-forecast.json") })
|
|
@@ -7109,9 +7299,21 @@ function apply(ctx, config = {}) {
|
|
|
7109
7299
|
sketchBridge.dispose();
|
|
7110
7300
|
};
|
|
7111
7301
|
}, "codex-subscription: native sketch tool");
|
|
7302
|
+
const accountUsageReader = createAccountUsageReader({
|
|
7303
|
+
vault: accountVault,
|
|
7304
|
+
createModels: (credentials) => {
|
|
7305
|
+
const accountModels = createModels({ credentials });
|
|
7306
|
+
accountModels.setProvider(provider);
|
|
7307
|
+
return accountModels;
|
|
7308
|
+
},
|
|
7309
|
+
fetch: (input, init) => network.fetch("quota", input, init),
|
|
7310
|
+
expirySkewMs: OAUTH_EXPIRY_SKEW_MS,
|
|
7311
|
+
activeReader: baseUsageReader
|
|
7312
|
+
});
|
|
7112
7313
|
const subscriptionHandler = createSubscriptionRpcHandler({
|
|
7113
7314
|
authHandler: createCodexRpcHandler(coordinator, { openExternal: openCodexAuthUrl }),
|
|
7114
7315
|
usageReader,
|
|
7316
|
+
accountUsageReader,
|
|
7115
7317
|
resetCreditService,
|
|
7116
7318
|
preferences,
|
|
7117
7319
|
defaultModel,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-codex-subscription",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
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",
|