dsh-codex-subscription 2.6.8 → 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 +494 -47
- 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.8
|
|
|
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
|
@@ -12,7 +12,7 @@ import { clientRequestSchema } from "@deepseek-ai/dsh-client-connection";
|
|
|
12
12
|
import { lstat, mkdir, open, readFile, rename, rm, stat } from "node:fs/promises";
|
|
13
13
|
import { constants, readFileSync } from "node:fs";
|
|
14
14
|
import { dirname, join, resolve } from "node:path";
|
|
15
|
-
import { pathToFileURL } from "node:url";
|
|
15
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
16
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
17
17
|
import * as dshLlm from "@deepseek-ai/dsh-llm";
|
|
18
18
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
@@ -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}`);
|
|
@@ -2770,6 +2799,37 @@ function retryPolicyFor(choice, resolve) {
|
|
|
2770
2799
|
}
|
|
2771
2800
|
}
|
|
2772
2801
|
//#endregion
|
|
2802
|
+
//#region src/request-observer.js
|
|
2803
|
+
const failureOf = (chunk) => chunk?.reason?.kind === "error" ? chunk.reason.failure : void 0;
|
|
2804
|
+
function observed(stream, options, provider) {
|
|
2805
|
+
return (async function* () {
|
|
2806
|
+
try {
|
|
2807
|
+
for await (const chunk of stream(options)) {
|
|
2808
|
+
const failure = failureOf(chunk);
|
|
2809
|
+
if (failure !== void 0 && !options?.signal?.aborted) provider.noteAdapterFailure?.("failure", options, failure);
|
|
2810
|
+
yield chunk;
|
|
2811
|
+
}
|
|
2812
|
+
} catch (error) {
|
|
2813
|
+
if (!options?.signal?.aborted) provider.noteAdapterFailure?.("thrown", options, error);
|
|
2814
|
+
throw error;
|
|
2815
|
+
}
|
|
2816
|
+
})();
|
|
2817
|
+
}
|
|
2818
|
+
function observeAdapter(adapter, provider) {
|
|
2819
|
+
return new Proxy(adapter, { get(target, property) {
|
|
2820
|
+
if (property === "stream") return (options) => observed((o) => target.stream(o), options, provider);
|
|
2821
|
+
if (property === "prepareCall") return async (...args) => {
|
|
2822
|
+
const call = await target.prepareCall(...args);
|
|
2823
|
+
return {
|
|
2824
|
+
...call,
|
|
2825
|
+
stream: (options) => observed((o) => call.stream(o), options, provider)
|
|
2826
|
+
};
|
|
2827
|
+
};
|
|
2828
|
+
const value = Reflect.get(target, property, target);
|
|
2829
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
2830
|
+
} });
|
|
2831
|
+
}
|
|
2832
|
+
//#endregion
|
|
2773
2833
|
//#region src/account-vault.js
|
|
2774
2834
|
const VERSION = 1;
|
|
2775
2835
|
const DEFAULT_LABEL = "Account 1";
|
|
@@ -2969,6 +3029,42 @@ var DshOAuthAccountVault = class {
|
|
|
2969
3029
|
return clone$1(payload?.accounts.find((account) => account.id === payload.activeId)?.credential);
|
|
2970
3030
|
});
|
|
2971
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
|
+
}
|
|
2972
3068
|
activeId() {
|
|
2973
3069
|
return this.#enqueue(async () => (await this.#ensurePayload())?.activeId);
|
|
2974
3070
|
}
|
|
@@ -3068,11 +3164,11 @@ var DshOAuthAccountVault = class {
|
|
|
3068
3164
|
};
|
|
3069
3165
|
//#endregion
|
|
3070
3166
|
//#region src/credential-store.js
|
|
3071
|
-
const PROVIDER$
|
|
3167
|
+
const PROVIDER$2 = "openai-codex";
|
|
3072
3168
|
const abortIfNeeded = (options) => options?.signal?.throwIfAborted();
|
|
3073
3169
|
const clone = (value) => value === void 0 ? void 0 : structuredClone(value);
|
|
3074
|
-
function assertProvider(providerId) {
|
|
3075
|
-
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)}`);
|
|
3076
3172
|
}
|
|
3077
3173
|
function assertOAuthCredential(value) {
|
|
3078
3174
|
if (value === void 0) return void 0;
|
|
@@ -3105,7 +3201,7 @@ var DshOAuthCredentialStore = class {
|
|
|
3105
3201
|
this.vault = options.vault;
|
|
3106
3202
|
}
|
|
3107
3203
|
#enqueue(providerId, operation, options) {
|
|
3108
|
-
assertProvider(providerId);
|
|
3204
|
+
assertProvider$1(providerId);
|
|
3109
3205
|
const current = (this.#chains.get(providerId) ?? Promise.resolve()).catch(() => void 0).then(async () => {
|
|
3110
3206
|
abortIfNeeded(options);
|
|
3111
3207
|
return operation();
|
|
@@ -3118,7 +3214,7 @@ var DshOAuthCredentialStore = class {
|
|
|
3118
3214
|
return current;
|
|
3119
3215
|
}
|
|
3120
3216
|
async #read(providerId, options) {
|
|
3121
|
-
assertProvider(providerId);
|
|
3217
|
+
assertProvider$1(providerId);
|
|
3122
3218
|
abortIfNeeded(options);
|
|
3123
3219
|
if (this.vault !== void 0) {
|
|
3124
3220
|
const current = await this.vault.readActive();
|
|
@@ -3151,8 +3247,8 @@ var DshOAuthCredentialStore = class {
|
|
|
3151
3247
|
}
|
|
3152
3248
|
async list(options) {
|
|
3153
3249
|
abortIfNeeded(options);
|
|
3154
|
-
return await this.read(PROVIDER$
|
|
3155
|
-
providerId: PROVIDER$
|
|
3250
|
+
return await this.read(PROVIDER$2, options) === void 0 ? [] : [{
|
|
3251
|
+
providerId: PROVIDER$2,
|
|
3156
3252
|
type: "oauth"
|
|
3157
3253
|
}];
|
|
3158
3254
|
}
|
|
@@ -3202,16 +3298,16 @@ function createCodexAuthService(models, store, options = {}) {
|
|
|
3202
3298
|
const createPendingStore = options.createPendingStore ?? (() => new PendingOAuthCredentialStore());
|
|
3203
3299
|
return Object.freeze({
|
|
3204
3300
|
async status(options) {
|
|
3205
|
-
const current = await store.read(PROVIDER$
|
|
3301
|
+
const current = await store.read(PROVIDER$2, options);
|
|
3206
3302
|
const accounts = await accountVault?.list();
|
|
3207
3303
|
if (current === void 0) return {
|
|
3208
3304
|
authenticated: false,
|
|
3209
|
-
provider: PROVIDER$
|
|
3305
|
+
provider: PROVIDER$2,
|
|
3210
3306
|
...accounts === void 0 ? {} : { accounts }
|
|
3211
3307
|
};
|
|
3212
3308
|
return {
|
|
3213
3309
|
authenticated: true,
|
|
3214
|
-
provider: PROVIDER$
|
|
3310
|
+
provider: PROVIDER$2,
|
|
3215
3311
|
type: "oauth",
|
|
3216
3312
|
expiresAt: current.expires,
|
|
3217
3313
|
...accounts === void 0 ? {} : { accounts }
|
|
@@ -3222,13 +3318,13 @@ function createCodexAuthService(models, store, options = {}) {
|
|
|
3222
3318
|
if (accountVault === void 0 || createLoginModels === void 0) throw new Error("Codex multi-account is unavailable");
|
|
3223
3319
|
return runLogin(async () => {
|
|
3224
3320
|
const pending = createPendingStore();
|
|
3225
|
-
await createLoginModels(pending).login(PROVIDER$
|
|
3321
|
+
await createLoginModels(pending).login(PROVIDER$2, "oauth", interaction);
|
|
3226
3322
|
const credential = pending.credential();
|
|
3227
3323
|
if (credential === void 0) throw new Error("Codex login did not return credentials");
|
|
3228
3324
|
await accountVault.add(input.label, credential);
|
|
3229
3325
|
});
|
|
3230
3326
|
}
|
|
3231
|
-
return runLogin(() => models.login(PROVIDER$
|
|
3327
|
+
return runLogin(() => models.login(PROVIDER$2, "oauth", interaction));
|
|
3232
3328
|
},
|
|
3233
3329
|
async select(id) {
|
|
3234
3330
|
if (accountVault === void 0) throw new Error("Codex multi-account is unavailable");
|
|
@@ -3241,7 +3337,7 @@ function createCodexAuthService(models, store, options = {}) {
|
|
|
3241
3337
|
return this.status();
|
|
3242
3338
|
},
|
|
3243
3339
|
logout(options) {
|
|
3244
|
-
return models.logout(PROVIDER$
|
|
3340
|
+
return models.logout(PROVIDER$2, options);
|
|
3245
3341
|
}
|
|
3246
3342
|
});
|
|
3247
3343
|
}
|
|
@@ -3644,13 +3740,146 @@ function createCodexRpcHandler(coordinator, options = {}) {
|
|
|
3644
3740
|
};
|
|
3645
3741
|
}
|
|
3646
3742
|
//#endregion
|
|
3743
|
+
//#region src/failure-capture.js
|
|
3744
|
+
const KEEP = /* @__PURE__ */ new Set([
|
|
3745
|
+
"method",
|
|
3746
|
+
"type",
|
|
3747
|
+
"role",
|
|
3748
|
+
"name",
|
|
3749
|
+
"api",
|
|
3750
|
+
"provider",
|
|
3751
|
+
"model",
|
|
3752
|
+
"stopReason",
|
|
3753
|
+
"reasoning",
|
|
3754
|
+
"reasoningEffort",
|
|
3755
|
+
"transport",
|
|
3756
|
+
"mimeType",
|
|
3757
|
+
"kind",
|
|
3758
|
+
"effort",
|
|
3759
|
+
"toolChoice",
|
|
3760
|
+
"cacheRetention",
|
|
3761
|
+
"textVerbosity",
|
|
3762
|
+
"serviceTier"
|
|
3763
|
+
]);
|
|
3764
|
+
const DROP = /* @__PURE__ */ new Set([
|
|
3765
|
+
"apiKey",
|
|
3766
|
+
"headers",
|
|
3767
|
+
"env",
|
|
3768
|
+
"authorization",
|
|
3769
|
+
"token",
|
|
3770
|
+
"signal",
|
|
3771
|
+
"fetch",
|
|
3772
|
+
"cookie"
|
|
3773
|
+
]);
|
|
3774
|
+
const MAX_DEPTH = 10;
|
|
3775
|
+
const MAX_ARRAY = 40;
|
|
3776
|
+
const MAX_KEYS = 80;
|
|
3777
|
+
const MAX_NODES = 4e3;
|
|
3778
|
+
const MAX_TEXT = 64;
|
|
3779
|
+
const UNDEFINED_MARKER = "$undefined";
|
|
3780
|
+
function redactRequest(value) {
|
|
3781
|
+
let nodes = 0;
|
|
3782
|
+
const walk = (item, key, depth) => {
|
|
3783
|
+
nodes += 1;
|
|
3784
|
+
if (nodes > MAX_NODES) return { $truncated: "nodes" };
|
|
3785
|
+
if (item === void 0) return { [UNDEFINED_MARKER]: true };
|
|
3786
|
+
if (item === null || typeof item === "boolean") return item;
|
|
3787
|
+
if (typeof item === "number") return Number.isFinite(item) ? item : { $number: String(item) };
|
|
3788
|
+
if (typeof item === "bigint" || typeof item === "symbol") return { $type: typeof item };
|
|
3789
|
+
if (typeof item === "function") return { $type: "function" };
|
|
3790
|
+
if (typeof item === "string") {
|
|
3791
|
+
if (KEEP.has(key) && item.length <= MAX_TEXT) return item;
|
|
3792
|
+
return item.length <= MAX_TEXT ? "x".repeat(item.length) : { $text: item.length };
|
|
3793
|
+
}
|
|
3794
|
+
if (depth >= MAX_DEPTH) return { $truncated: "depth" };
|
|
3795
|
+
if (Array.isArray(item)) {
|
|
3796
|
+
const list = [];
|
|
3797
|
+
for (let index = 0; index < Math.min(item.length, MAX_ARRAY); index += 1) list.push(index in item ? walk(item[index], key, depth + 1) : { $hole: true });
|
|
3798
|
+
return item.length > MAX_ARRAY ? [...list, { $more: item.length - MAX_ARRAY }] : list;
|
|
3799
|
+
}
|
|
3800
|
+
if (item instanceof Uint8Array) return { $bytes: item.byteLength };
|
|
3801
|
+
if (typeof item === "object") {
|
|
3802
|
+
const entries = Object.keys(item).filter((name) => !DROP.has(name));
|
|
3803
|
+
const out = {};
|
|
3804
|
+
for (const name of entries.slice(0, MAX_KEYS)) out[name] = walk(item[name], name, depth + 1);
|
|
3805
|
+
if (entries.length > MAX_KEYS) out.$moreKeys = entries.length - MAX_KEYS;
|
|
3806
|
+
return out;
|
|
3807
|
+
}
|
|
3808
|
+
return { $type: typeof item };
|
|
3809
|
+
};
|
|
3810
|
+
return walk(value, "", 0);
|
|
3811
|
+
}
|
|
3812
|
+
//#endregion
|
|
3813
|
+
//#region src/pi-ai-compat.js
|
|
3814
|
+
const versionOf = (specifier) => {
|
|
3815
|
+
try {
|
|
3816
|
+
let path = fileURLToPath(import.meta.resolve(specifier));
|
|
3817
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
3818
|
+
path = path.replace(/[\\/][^\\/]+$/u, "");
|
|
3819
|
+
try {
|
|
3820
|
+
const manifest = JSON.parse(readFileSync(`${path}/package.json`, "utf8"));
|
|
3821
|
+
if (typeof manifest.version === "string") return manifest.version;
|
|
3822
|
+
} catch {}
|
|
3823
|
+
}
|
|
3824
|
+
} catch {}
|
|
3825
|
+
};
|
|
3826
|
+
let cached;
|
|
3827
|
+
/** Versions of the pi-ai library and of DSH's adapter this plugin runs against. */
|
|
3828
|
+
function runtimeVersions() {
|
|
3829
|
+
cached ??= {
|
|
3830
|
+
piAi: versionOf("@earendil-works/pi-ai"),
|
|
3831
|
+
adapter: versionOf("@deepseek-ai/dsh-llm-pi-ai")
|
|
3832
|
+
};
|
|
3833
|
+
return cached;
|
|
3834
|
+
}
|
|
3835
|
+
const atLeast087 = (version) => {
|
|
3836
|
+
const match = /^(\d+)\.(\d+)/u.exec(version ?? "");
|
|
3837
|
+
return match !== null && (Number(match[1]) > 0 || Number(match[2]) >= 87);
|
|
3838
|
+
};
|
|
3839
|
+
/** Whether the installed pi-ai understands system messages inside the transcript. */
|
|
3840
|
+
const understandsSystemMessages = (version) => atLeast087(version);
|
|
3841
|
+
const textOf = (content) => typeof content === "string" ? content : Array.isArray(content) ? content.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n") : "";
|
|
3842
|
+
/**
|
|
3843
|
+
* Fold the transcript's leading system messages back into `systemPrompt` and `tools`,
|
|
3844
|
+
* the form every pi-ai release accepts. Messages after the first non-system one stay.
|
|
3845
|
+
*/
|
|
3846
|
+
function hoistLeadingSystem(context) {
|
|
3847
|
+
const messages = context?.messages;
|
|
3848
|
+
if (!Array.isArray(messages) || messages[0]?.role !== "system") return context;
|
|
3849
|
+
let end = 0;
|
|
3850
|
+
const texts = context.systemPrompt ? [context.systemPrompt] : [];
|
|
3851
|
+
const tools = [...Array.isArray(context.tools) ? context.tools : []];
|
|
3852
|
+
while (messages[end]?.role === "system") {
|
|
3853
|
+
const message = messages[end];
|
|
3854
|
+
const text = [textOf(message.content), ...Object.values(message.sections ?? {}).filter((value) => typeof value === "string")].filter(Boolean).join("\n\n");
|
|
3855
|
+
if (text !== "") texts.push(text);
|
|
3856
|
+
if (Array.isArray(message.toolsAdded)) tools.push(...message.toolsAdded);
|
|
3857
|
+
end += 1;
|
|
3858
|
+
}
|
|
3859
|
+
const { systemPrompt: _systemPrompt, tools: _tools, ...rest } = context;
|
|
3860
|
+
const systemPrompt = texts.join("\n\n");
|
|
3861
|
+
return {
|
|
3862
|
+
...rest,
|
|
3863
|
+
...systemPrompt === "" ? {} : { systemPrompt },
|
|
3864
|
+
...tools.length === 0 ? {} : { tools },
|
|
3865
|
+
messages: messages.slice(end)
|
|
3866
|
+
};
|
|
3867
|
+
}
|
|
3868
|
+
//#endregion
|
|
3647
3869
|
//#region src/transport-failure.js
|
|
3648
|
-
const TRANSIENT_WEBSOCKET_CLOSE = /^WebSocket closed (?:1006|1011|1012|1013)(?:\s|$)/;
|
|
3870
|
+
const TRANSIENT_WEBSOCKET_CLOSE = /^WebSocket closed (?:1000|1006|1011|1012|1013)(?:\s|$)/;
|
|
3649
3871
|
const GATEWAY_REPLAY_BUFFER_EXCEEDED = /^\s*exceeded request buffer limit while retrying upstream\s*$/;
|
|
3650
3872
|
function normalizeTransportEvent(event, signal) {
|
|
3651
3873
|
if (signal?.aborted || event?.type !== "error" || event.reason !== "error") return event;
|
|
3652
3874
|
const message = event.error?.errorMessage;
|
|
3653
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
|
+
};
|
|
3654
3883
|
if (!TRANSIENT_WEBSOCKET_CLOSE.test(message) && !GATEWAY_REPLAY_BUFFER_EXCEEDED.test(message)) return event;
|
|
3655
3884
|
return {
|
|
3656
3885
|
...event,
|
|
@@ -3806,6 +4035,36 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
3806
4035
|
sessionModel: 0,
|
|
3807
4036
|
retries: 0
|
|
3808
4037
|
};
|
|
4038
|
+
const streamFailure = {
|
|
4039
|
+
count: 0,
|
|
4040
|
+
stage: void 0,
|
|
4041
|
+
frame: void 0,
|
|
4042
|
+
request: void 0,
|
|
4043
|
+
adapter: void 0
|
|
4044
|
+
};
|
|
4045
|
+
const noteAdapterFailure = (kind, adapterOptions, failure) => {
|
|
4046
|
+
try {
|
|
4047
|
+
streamFailure.adapter = {
|
|
4048
|
+
kind,
|
|
4049
|
+
...typeof failure?.code === "string" ? { code: failure.code.slice(0, 40) } : {},
|
|
4050
|
+
message: String(failure?.message ?? "").slice(0, 160),
|
|
4051
|
+
request: redactRequest(adapterOptions)
|
|
4052
|
+
};
|
|
4053
|
+
} catch {
|
|
4054
|
+
streamFailure.adapter = void 0;
|
|
4055
|
+
}
|
|
4056
|
+
};
|
|
4057
|
+
const noteStreamFailure = (stage, error, source) => {
|
|
4058
|
+
streamFailure.count += 1;
|
|
4059
|
+
streamFailure.stage = stage;
|
|
4060
|
+
try {
|
|
4061
|
+
streamFailure.request = source === void 0 ? void 0 : redactRequest(source);
|
|
4062
|
+
} catch {
|
|
4063
|
+
streamFailure.request = void 0;
|
|
4064
|
+
}
|
|
4065
|
+
const frame = typeof error?.stack === "string" ? /([\w.@-]+[\\/][\w.-]+\.[cm]?[jt]sx?):(\d+):(\d+)/u.exec(error.stack.split("\n").slice(1).join("\n")) : null;
|
|
4066
|
+
streamFailure.frame = frame ? `${frame[1].replaceAll("\\", "/")}:${frame[2]}:${frame[3]}` : void 0;
|
|
4067
|
+
};
|
|
3809
4068
|
const reviewRoute = (model, context) => {
|
|
3810
4069
|
if (resolveReviewModel() !== "official" || !isDshAutoReview(context)) return void 0;
|
|
3811
4070
|
review.requests += 1;
|
|
@@ -3860,22 +4119,40 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
3860
4119
|
};
|
|
3861
4120
|
return { [Symbol.asyncIterator]: () => replay ??= run() };
|
|
3862
4121
|
};
|
|
3863
|
-
const networkIterable = (factory, options, direct = false) => {
|
|
4122
|
+
const networkIterable = (factory, options, direct = false, source) => {
|
|
3864
4123
|
let iterator;
|
|
3865
4124
|
let prepared;
|
|
4125
|
+
let stage = "prepare";
|
|
3866
4126
|
const step = async (method, value) => {
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
4127
|
+
try {
|
|
4128
|
+
const request = await (prepared ??= (direct ? void 0 : connection?.prepare(options)) ?? Promise.resolve({ options }));
|
|
4129
|
+
if (stage === "prepare") stage = "request";
|
|
4130
|
+
const result = await runNetwork("model", () => {
|
|
4131
|
+
const next = direct ? request.options : compaction?.requestOptions(request.options) ?? request.options;
|
|
4132
|
+
iterator ??= factory({
|
|
4133
|
+
...next,
|
|
4134
|
+
async onPayload(payload, requestModel) {
|
|
4135
|
+
stage = "payload";
|
|
4136
|
+
return next.onPayload?.(payload, requestModel);
|
|
4137
|
+
}
|
|
4138
|
+
})[Symbol.asyncIterator]();
|
|
4139
|
+
return iterator[method]?.(value) ?? (method === "throw" ? Promise.reject(value) : Promise.resolve({
|
|
4140
|
+
done: true,
|
|
4141
|
+
value
|
|
4142
|
+
}));
|
|
4143
|
+
}, direct ? request.network : compaction?.networkOptions(request.network) ?? request.network);
|
|
4144
|
+
if (!result.done) {
|
|
4145
|
+
if (result.value?.type === "error" && !request.options?.signal?.aborted) noteStreamFailure(stage, void 0, source);
|
|
4146
|
+
else if (stage === "payload") stage = "stream";
|
|
4147
|
+
}
|
|
4148
|
+
return result.done ? result : {
|
|
4149
|
+
...result,
|
|
4150
|
+
value: normalizeTransportEvent(result.value, request.options?.signal)
|
|
4151
|
+
};
|
|
4152
|
+
} catch (error) {
|
|
4153
|
+
noteStreamFailure(stage, error, source);
|
|
4154
|
+
throw error;
|
|
4155
|
+
}
|
|
3879
4156
|
};
|
|
3880
4157
|
return {
|
|
3881
4158
|
[Symbol.asyncIterator]() {
|
|
@@ -3889,9 +4166,16 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
3889
4166
|
throw: (error) => iterator ? step("throw", error) : Promise.reject(error)
|
|
3890
4167
|
};
|
|
3891
4168
|
};
|
|
3892
|
-
const routed = (method, model,
|
|
4169
|
+
const routed = (method, model, transcript, options) => {
|
|
4170
|
+
const context = understandsSystemMessages(runtimeVersions().piAi) ? transcript : hoistLeadingSystem(transcript);
|
|
3893
4171
|
const route = reviewRoute(model, context);
|
|
3894
|
-
const
|
|
4172
|
+
const source = {
|
|
4173
|
+
method,
|
|
4174
|
+
model,
|
|
4175
|
+
context,
|
|
4176
|
+
options
|
|
4177
|
+
};
|
|
4178
|
+
const create = () => route === void 0 ? networkIterable((prepared) => provider[method](model, context, prepared), withPreferences(model, options), false, source) : networkIterable((prepared) => provider[method](model, context, prepared), withReview(route, options), true, source);
|
|
3895
4179
|
return isDshAutoReview(context) ? reviewIterable(create) : create();
|
|
3896
4180
|
};
|
|
3897
4181
|
return Object.freeze({
|
|
@@ -3903,7 +4187,9 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
3903
4187
|
getModels,
|
|
3904
4188
|
stream: (model, context, options) => routed("stream", model, context, options),
|
|
3905
4189
|
streamSimple: (model, context, options) => routed("streamSimple", model, context, options),
|
|
3906
|
-
reviewCounters: () => ({ ...review })
|
|
4190
|
+
reviewCounters: () => ({ ...review }),
|
|
4191
|
+
streamFailure: () => ({ ...streamFailure }),
|
|
4192
|
+
noteAdapterFailure
|
|
3907
4193
|
});
|
|
3908
4194
|
}
|
|
3909
4195
|
Object.freeze([
|
|
@@ -4318,7 +4604,10 @@ function createCodexSearchProvider(options) {
|
|
|
4318
4604
|
if (signal?.aborted || error?.name === "AbortError") throw new WebError("Codex search aborted", "WEB_ABORTED", { cause: error });
|
|
4319
4605
|
throw new WebError("Codex search request failed", "WEB_PROVIDER_ERROR", { cause: error });
|
|
4320
4606
|
}
|
|
4321
|
-
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
|
+
}
|
|
4322
4611
|
let value;
|
|
4323
4612
|
try {
|
|
4324
4613
|
value = await response.json();
|
|
@@ -4849,6 +5138,15 @@ function safeRequests(network) {
|
|
|
4849
5138
|
}
|
|
4850
5139
|
return result;
|
|
4851
5140
|
}
|
|
5141
|
+
/** pi-ai and DSH's adapter come from the host; a profile can pair an adapter with an older library, so the report names both. */
|
|
5142
|
+
function libraryVersions() {
|
|
5143
|
+
const { piAi, adapter } = runtimeVersions();
|
|
5144
|
+
const valid = (value) => typeof value === "string" && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,32})?$/u.test(value);
|
|
5145
|
+
return {
|
|
5146
|
+
...valid(piAi) ? { piAi } : {},
|
|
5147
|
+
...valid(adapter) ? { adapter } : {}
|
|
5148
|
+
};
|
|
5149
|
+
}
|
|
4852
5150
|
/** Build a support report that deliberately excludes OAuth and account metadata. */
|
|
4853
5151
|
async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" }, network, modelCatalog, connection, compaction, review, inspectionOptions, operations, runtimeManagement, storage, tools }) {
|
|
4854
5152
|
const collected = {};
|
|
@@ -4914,6 +5212,26 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
|
|
|
4914
5212
|
"retries"
|
|
4915
5213
|
]) : void 0
|
|
4916
5214
|
],
|
|
5215
|
+
[
|
|
5216
|
+
"streamFailure",
|
|
5217
|
+
"transport",
|
|
5218
|
+
() => {
|
|
5219
|
+
const value = review?.streamFailure?.();
|
|
5220
|
+
if (!Number.isSafeInteger(value?.count)) return void 0;
|
|
5221
|
+
return {
|
|
5222
|
+
count: value.count,
|
|
5223
|
+
...[
|
|
5224
|
+
"prepare",
|
|
5225
|
+
"request",
|
|
5226
|
+
"payload",
|
|
5227
|
+
"stream"
|
|
5228
|
+
].includes(value.stage) ? { stage: value.stage } : {},
|
|
5229
|
+
...typeof value.frame === "string" && /^[\w.@/-]{1,120}:\d+:\d+$/u.test(value.frame) ? { frame: value.frame } : {},
|
|
5230
|
+
...value.request && typeof value.request === "object" && JSON.stringify(value.request).length <= 12e4 ? { request: value.request } : {},
|
|
5231
|
+
...value.adapter && typeof value.adapter === "object" && JSON.stringify(value.adapter).length <= 12e4 ? { adapter: value.adapter } : {}
|
|
5232
|
+
};
|
|
5233
|
+
}
|
|
5234
|
+
],
|
|
4917
5235
|
[
|
|
4918
5236
|
"operations",
|
|
4919
5237
|
"transport",
|
|
@@ -5007,7 +5325,8 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
|
|
|
5007
5325
|
node: process.version,
|
|
5008
5326
|
platform: process.platform,
|
|
5009
5327
|
arch: process.arch,
|
|
5010
|
-
...hostDshVersion() ? { dsh: hostDshVersion() } : {}
|
|
5328
|
+
...hostDshVersion() ? { dsh: hostDshVersion() } : {},
|
|
5329
|
+
...libraryVersions()
|
|
5011
5330
|
},
|
|
5012
5331
|
account,
|
|
5013
5332
|
login,
|
|
@@ -5017,6 +5336,7 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
|
|
|
5017
5336
|
...collected.websocket ? { websocket: collected.websocket } : {},
|
|
5018
5337
|
...collected.compaction ? { compaction: collected.compaction } : {},
|
|
5019
5338
|
...collected.review ? { review: collected.review } : {},
|
|
5339
|
+
...collected.streamFailure ? { streamFailure: collected.streamFailure } : {},
|
|
5020
5340
|
...catalog && ["unavailable", "online"].includes(catalog.source) && [
|
|
5021
5341
|
"idle",
|
|
5022
5342
|
"refreshing",
|
|
@@ -5286,7 +5606,7 @@ function createCodexUsageReader(options) {
|
|
|
5286
5606
|
signal: requestSignal$1(signal, timeoutMs)
|
|
5287
5607
|
});
|
|
5288
5608
|
if (!response.ok) {
|
|
5289
|
-
const error =
|
|
5609
|
+
const error = new Error(await authFailureMessage(response) ?? `ChatGPT usage request failed (HTTP ${response.status})`);
|
|
5290
5610
|
Object.defineProperty(error, "retryAfterMs", { value: retryAfterMs(response, now, maxRetryAfterMs) });
|
|
5291
5611
|
throw error;
|
|
5292
5612
|
}
|
|
@@ -5337,6 +5657,102 @@ function createCodexUsageReader(options) {
|
|
|
5337
5657
|
});
|
|
5338
5658
|
}
|
|
5339
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
|
|
5340
5756
|
//#region src/quota-retry.js
|
|
5341
5757
|
const DEFAULT_MAX_WAIT_MS = 360 * 60 * 1e3;
|
|
5342
5758
|
const DEFAULT_RESET_MARGIN_MS = 1e4;
|
|
@@ -6057,7 +6473,7 @@ function createCodexResetCreditService(options) {
|
|
|
6057
6473
|
},
|
|
6058
6474
|
signal: requestSignal(signal, timeoutMs)
|
|
6059
6475
|
});
|
|
6060
|
-
if (!response.ok) throw new Error(
|
|
6476
|
+
if (!response.ok) throw new Error(await authFailureMessage(response) ?? `ChatGPT quota reset request failed (HTTP ${response.status})`);
|
|
6061
6477
|
let raw;
|
|
6062
6478
|
try {
|
|
6063
6479
|
raw = await response.json();
|
|
@@ -6191,7 +6607,7 @@ function createCodexResetCreditService(options) {
|
|
|
6191
6607
|
throw new Error(UNCERTAIN_RESET_RESULT);
|
|
6192
6608
|
}
|
|
6193
6609
|
if (response.status !== 401 && response.status !== 403) retryable = false;
|
|
6194
|
-
throw new Error(
|
|
6610
|
+
throw new Error(await authFailureMessage(response) ?? `ChatGPT quota reset request failed (HTTP ${response.status})`);
|
|
6195
6611
|
}
|
|
6196
6612
|
let raw;
|
|
6197
6613
|
try {
|
|
@@ -6233,7 +6649,7 @@ const publicError = (code, message) => ({
|
|
|
6233
6649
|
details: { issues: [] }
|
|
6234
6650
|
}
|
|
6235
6651
|
});
|
|
6236
|
-
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 }) {
|
|
6237
6653
|
return async (endpoint, payload, signal) => {
|
|
6238
6654
|
if (endpoint === "default-model/status" || endpoint === "default-model/select") try {
|
|
6239
6655
|
signal.throwIfAborted();
|
|
@@ -6380,10 +6796,27 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
6380
6796
|
};
|
|
6381
6797
|
} catch (error) {
|
|
6382
6798
|
if (signal.aborted) throw error;
|
|
6383
|
-
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
|
+
]);
|
|
6384
6804
|
const message = error instanceof Error && known.has(error.message) ? error.message : "Could not read ChatGPT usage";
|
|
6385
6805
|
return publicError("internal", message);
|
|
6386
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
|
+
}
|
|
6387
6820
|
if (endpoint === "reset-credit/inspect" || endpoint === "reset-credit/prepare" || endpoint === "reset-credit/consume") try {
|
|
6388
6821
|
signal.throwIfAborted();
|
|
6389
6822
|
return {
|
|
@@ -6402,6 +6835,7 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
6402
6835
|
const known = /* @__PURE__ */ new Set([
|
|
6403
6836
|
"ChatGPT subscription is not signed in",
|
|
6404
6837
|
"ChatGPT sign-in needs to be renewed",
|
|
6838
|
+
REQUEST_BLOCKED,
|
|
6405
6839
|
"No quota reset is available",
|
|
6406
6840
|
"No usable quota reset is available",
|
|
6407
6841
|
"The available quota reset expires too soon",
|
|
@@ -6424,7 +6858,7 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
6424
6858
|
() => modelCatalog?.clear(),
|
|
6425
6859
|
() => onAccountChanged?.()
|
|
6426
6860
|
];
|
|
6427
|
-
if (endpoint === "logout") tasks.push(() => usageReader.clear());
|
|
6861
|
+
if (endpoint === "logout") tasks.push(() => usageReader.clear(), () => accountUsageReader?.clear());
|
|
6428
6862
|
if (endpoint === "account/remove" && typeof payload?.id === "string") tasks.push(() => usageReader.clearScope(payload.id));
|
|
6429
6863
|
if ((await Promise.allSettled(tasks.map((task) => Promise.resolve().then(task)))).some((value) => value.status === "rejected")) try {
|
|
6430
6864
|
onCleanupFailure?.();
|
|
@@ -6745,7 +7179,7 @@ function apply(ctx, config = {}) {
|
|
|
6745
7179
|
auth: adapterAuth,
|
|
6746
7180
|
resolveAttachments: () => ctx.get?.("attachments")
|
|
6747
7181
|
});
|
|
6748
|
-
ctx.llm.registerAdapter([PROVIDER], compaction.wrapAdapter(adapter));
|
|
7182
|
+
ctx.llm.registerAdapter([PROVIDER], observeAdapter(compaction.wrapAdapter(adapter), provider));
|
|
6749
7183
|
const currentAgent = () => ctx.get?.("agents")?.currentInitiator?.();
|
|
6750
7184
|
const codexSearch = createCodexSearchProvider({
|
|
6751
7185
|
resolvePreferences: () => readCapabilitySettings(settings.get()),
|
|
@@ -6785,12 +7219,13 @@ function apply(ctx, config = {}) {
|
|
|
6785
7219
|
}
|
|
6786
7220
|
});
|
|
6787
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
|
+
});
|
|
6788
7227
|
const usageReader = createQuotaForecastReader({
|
|
6789
|
-
reader:
|
|
6790
|
-
getAuth: resolveAuth,
|
|
6791
|
-
readCredential: (options) => store.read(PROVIDER, options),
|
|
6792
|
-
fetch: (input, init) => network.fetch("quota", input, init)
|
|
6793
|
-
}),
|
|
7228
|
+
reader: baseUsageReader,
|
|
6794
7229
|
enabled: () => normalizeQuickQuotaMode(settings.get()[QUICK_QUOTA_MODE_FIELD], settings.get()[LEGACY_QUICK_QUOTA_FIELD]) === QUICK_QUOTA_MODE_FORECAST,
|
|
6795
7230
|
scope: async () => await accountVault?.activeId() ?? "legacy",
|
|
6796
7231
|
stateStore: new QuotaForecastStateStore({ filename: dshHomePath("state", "codex-subscription", "quota-forecast.json") })
|
|
@@ -6864,9 +7299,21 @@ function apply(ctx, config = {}) {
|
|
|
6864
7299
|
sketchBridge.dispose();
|
|
6865
7300
|
};
|
|
6866
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
|
+
});
|
|
6867
7313
|
const subscriptionHandler = createSubscriptionRpcHandler({
|
|
6868
7314
|
authHandler: createCodexRpcHandler(coordinator, { openExternal: openCodexAuthUrl }),
|
|
6869
7315
|
usageReader,
|
|
7316
|
+
accountUsageReader,
|
|
6870
7317
|
resetCreditService,
|
|
6871
7318
|
preferences,
|
|
6872
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",
|