dsh-claude-subscription 0.2.0 → 0.3.1
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/AGENTS.md +10 -5
- package/README.en.md +17 -14
- package/README.md +13 -12
- package/lib/client.js +57 -34
- package/lib/index.js +195 -42
- package/package.json +17 -23
- package/SECURITY.md +0 -31
- package/THIRD_PARTY_NOTICES.md +0 -12
package/AGENTS.md
CHANGED
|
@@ -6,7 +6,7 @@ Use this guide when a user asks an Agent to install, update, verify, or remove
|
|
|
6
6
|
## Safety
|
|
7
7
|
|
|
8
8
|
- Confirm the target DSH profile; use `web` only when it is the user's target.
|
|
9
|
-
- Use the pinned `v0.
|
|
9
|
+
- Use the pinned `v0.3.0` release assets for a first install, never a moving branch.
|
|
10
10
|
- Never print OAuth credentials, account IDs, authorization callbacks, or the
|
|
11
11
|
credential store.
|
|
12
12
|
- Do not start, stop, or restart DSH without explicit permission.
|
|
@@ -16,6 +16,10 @@ Use this guide when a user asks an Agent to install, update, verify, or remove
|
|
|
16
16
|
|
|
17
17
|
## Detect the installation
|
|
18
18
|
|
|
19
|
+
This plugin release supports only DeepSeek Harness `0.1.2-alpha.5`. Run
|
|
20
|
+
`dsh --version` first and stop on any other version; do not upgrade DSH or
|
|
21
|
+
ignore peer dependency warnings without explicit permission.
|
|
22
|
+
|
|
19
23
|
Check whether the plugin is already installed in the target DSH profile. When
|
|
20
24
|
it is already listed, use update or uninstall instead of installing again.
|
|
21
25
|
|
|
@@ -25,7 +29,7 @@ When `dsh`, Node.js, and pnpm are already available, install the pinned npm
|
|
|
25
29
|
package directly:
|
|
26
30
|
|
|
27
31
|
```sh
|
|
28
|
-
dsh plugin --profile web add dsh-claude-subscription@0.
|
|
32
|
+
dsh plugin --profile web add dsh-claude-subscription@0.3.0
|
|
29
33
|
```
|
|
30
34
|
|
|
31
35
|
Update with `dsh plugin --profile web update dsh-claude-subscription`.
|
|
@@ -42,7 +46,7 @@ profile (`desktop`, `web`):
|
|
|
42
46
|
|
|
43
47
|
1. `pnpm pack` the plugin into a tarball (or download the pinned release tarball).
|
|
44
48
|
2. Copy the tarball into the profile directory.
|
|
45
|
-
3. Add `"dsh-claude-subscription": "file:./dsh-claude-subscription-0.
|
|
49
|
+
3. Add `"dsh-claude-subscription": "file:./dsh-claude-subscription-0.3.0.tgz"` to
|
|
46
50
|
the profile's `package.json` `dependencies` and add `dsh-claude-subscription`
|
|
47
51
|
to its `dsh.profile.bundles` array.
|
|
48
52
|
4. Run `pnpm install` in the profile directory.
|
|
@@ -71,8 +75,9 @@ Success requires:
|
|
|
71
75
|
|
|
72
76
|
Do not treat `dsh plugin --profile web peers check` as the completion test.
|
|
73
77
|
If the user authorizes a live check, restart DSH manually, open
|
|
74
|
-
**Settings ->
|
|
75
|
-
|
|
78
|
+
**Settings -> Models**, verify the Anthropic card reports the saved login,
|
|
79
|
+
and check that **Settings -> Claude subscription** loads. Confirm that the
|
|
80
|
+
`anthropic` provider and its Claude models are
|
|
76
81
|
selectable in the model picker. Only when the user explicitly requests a
|
|
77
82
|
quota-consuming live check, ask the Agent to send one short message with a
|
|
78
83
|
Claude model and confirm the reply.
|
package/README.en.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# DSH Claude Subscription
|
|
2
2
|
|
|
3
|
-
[](https://github.com/x5427876/dsh-claude-subscription/actions/workflows/ci.yml)
|
|
4
3
|
[](https://www.npmjs.com/package/dsh-claude-subscription)
|
|
5
4
|
[](LICENSE)
|
|
6
5
|
|
|
@@ -16,7 +15,7 @@ DSH's host credential service: no token extraction, no resale, no extra billing.
|
|
|
16
15
|
## What it does
|
|
17
16
|
|
|
18
17
|
- Uses your Claude Pro / Max subscription directly inside DSH, with no Anthropic API key required;
|
|
19
|
-
- Signs in to claude.ai from Settings
|
|
18
|
+
- Signs in to claude.ai from the Anthropic card under **Settings -> Models** or from **Settings -> Claude subscription**, using the official OAuth flow and keeping credentials on the host;
|
|
20
19
|
- Refreshes the access token automatically with the refresh token;
|
|
21
20
|
- Shows sign-in status and token expiry;
|
|
22
21
|
- Shows the **subscription quota**: remaining percentage and reset time for the 5-hour, 7-day, Opus, and Sonnet windows;
|
|
@@ -25,10 +24,13 @@ DSH's host credential service: no token extraction, no resale, no extra billing.
|
|
|
25
24
|
|
|
26
25
|
## Prepare DSH
|
|
27
26
|
|
|
28
|
-
This plugin supports DeepSeek Harness `0.1.
|
|
29
|
-
requires a Claude Pro, Max, Team, or Enterprise subscription.
|
|
27
|
+
This plugin currently supports only DeepSeek Harness `0.1.2-alpha.5` and
|
|
28
|
+
requires a Claude Pro, Max, Team, or Enterprise subscription. DSH remains a
|
|
29
|
+
developer preview; an npm `latest` or `next` release other than
|
|
30
|
+
`0.1.2-alpha.5` is outside this plugin version's supported range.
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
Run `dsh --version` before installation. Do not merely ignore peer dependency
|
|
33
|
+
warnings.
|
|
32
34
|
|
|
33
35
|
## Install
|
|
34
36
|
|
|
@@ -59,22 +61,23 @@ For DSH Desktop, put the `pnpm pack` tarball into
|
|
|
59
61
|
`dsh.profile.bundles` entry in that profile's `package.json`, run
|
|
60
62
|
`pnpm install` there, then restart DSH. After install:
|
|
61
63
|
|
|
62
|
-
1. Open **Settings ->
|
|
64
|
+
1. Open **Settings -> Models** and find Anthropic. Full quota details remain
|
|
65
|
+
under **Settings -> Claude subscription**.
|
|
63
66
|
2. If not signed in, click **Browser sign-in** and authorize on claude.ai.
|
|
64
67
|
3. Pick a Claude model under the `anthropic` provider in the model picker.
|
|
65
68
|
4. Turn on **Composer quota** on the same page when you want it.
|
|
66
69
|
|
|
67
70
|
## Quota display
|
|
68
71
|
|
|
69
|
-
The
|
|
70
|
-
quota
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
The model picker shows only models returned by the Anthropic Models API. The
|
|
73
|
+
**Subscription quota** card in **Settings -> Claude subscription** shows only
|
|
74
|
+
windows and labels returned by the Anthropic usage API. The host reads them,
|
|
75
|
+
and the access token never leaves the machine. Results are cached for 60
|
|
76
|
+
seconds, and **Refresh** reads them again immediately.
|
|
73
77
|
|
|
74
|
-
With **Composer quota** on,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
reports no quota windows.
|
|
78
|
+
With **Composer quota** on, the input shows the tightest API window explicitly
|
|
79
|
+
marked as global or applicable to the current model. A window without API scope
|
|
80
|
+
metadata is not used for composer quota.
|
|
78
81
|
|
|
79
82
|
## Update and uninstall
|
|
80
83
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# DSH Claude Subscription
|
|
2
2
|
|
|
3
|
-
[](https://github.com/x5427876/dsh-claude-subscription/actions/workflows/ci.yml)
|
|
4
3
|
[](https://www.npmjs.com/package/dsh-claude-subscription)
|
|
5
4
|
[](LICENSE)
|
|
6
5
|
|
|
@@ -16,7 +15,7 @@
|
|
|
16
15
|
## 能做什麼
|
|
17
16
|
|
|
18
17
|
- 在 DSH 中直接使用 Claude Pro / Max 訂閱,不需要 Anthropic API Key;
|
|
19
|
-
-
|
|
18
|
+
- 在**設定 → 模型**的 Anthropic 卡片或**設定 → Claude 訂閱**中,以官方 OAuth 流程登入 claude.ai,憑證保留在 DSH 本機;
|
|
20
19
|
- access token 過期時自動以 refresh token 續期,不需手動處理;
|
|
21
20
|
- 顯示登入狀態;
|
|
22
21
|
- 顯示**訂閱額度**:5 小時、7 天、Opus 與 Sonnet 的剩餘百分比與重置時間;
|
|
@@ -25,10 +24,11 @@
|
|
|
25
24
|
|
|
26
25
|
## 準備 DSH
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
本插件目前僅適配 DeepSeek Harness `0.1.2-alpha.5`,需要一個 Claude Pro、
|
|
28
|
+
Max、Team 或 Enterprise 訂閱帳號。DSH 仍在 developer preview;npm 的
|
|
29
|
+
`latest` / `next` 通道若不是 `0.1.2-alpha.5`,不在本版本的支援範圍。
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
安裝前請先以 `dsh --version` 確認版本;不要只忽略 peer dependency 警告。
|
|
32
32
|
|
|
33
33
|
## 安裝
|
|
34
34
|
|
|
@@ -58,20 +58,21 @@ DSH Desktop(桌面版)時,把 `pnpm pack` 產出的 tarball 放進
|
|
|
58
58
|
`file:` 依賴與 `dsh.profile.bundles` 條目,然後在該目錄執行
|
|
59
59
|
`pnpm install`,最後重啟 DSH。安裝後:
|
|
60
60
|
|
|
61
|
-
1. 開啟 DSH 的**設定 →
|
|
61
|
+
1. 開啟 DSH 的**設定 → 模型**,找到 Anthropic;完整額度資訊仍位於
|
|
62
|
+
**設定 → Claude 訂閱**。
|
|
62
63
|
2. 若尚未登入,按**瀏覽器登入**,在 claude.ai 完成授權。
|
|
63
64
|
3. 在模型選單選擇 `anthropic` provider 下的 Claude 模型。
|
|
64
65
|
4. 需要時,在同一頁打開**輸入框額度**開關。
|
|
65
66
|
|
|
66
67
|
## 額度顯示
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
模型選單只顯示 Anthropic Models API 回傳的模型。**設定 → Claude 訂閱**的
|
|
70
|
+
**訂閱額度**卡片只顯示 Anthropic usage API 回傳的額度視窗與名稱。
|
|
71
|
+
資料由 host 端讀取,access token 不會離開本機。結果快取 60 秒,
|
|
72
|
+
按**重新整理**可立即重新讀取。
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
而沒有額度視窗時,該處不顯示任何內容。
|
|
74
|
+
打開**輸入框額度**後,輸入框右側顯示 API 明確標示為全域或適用於目前模型的
|
|
75
|
+
最緊額度視窗。API 未提供適用範圍時,該視窗不會用於輸入框額度。
|
|
75
76
|
|
|
76
77
|
## 更新與解除安裝
|
|
77
78
|
|
package/lib/client.js
CHANGED
|
@@ -8,22 +8,10 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
10
|
//#region src/composer-quota.js
|
|
11
|
-
const SHARED_WINDOW_IDS = Object.freeze(["five_hour", "seven_day"]);
|
|
12
|
-
const MODEL_WINDOW_IDS = Object.freeze({
|
|
13
|
-
opus: "seven_day_opus",
|
|
14
|
-
sonnet: "seven_day_sonnet"
|
|
15
|
-
});
|
|
16
11
|
const isDisplayableWindow = (window) => Number.isFinite(window?.remainingPercent) && window.remainingPercent >= 0 && window.remainingPercent <= 100;
|
|
17
|
-
|
|
18
|
-
function applicableIds(model) {
|
|
19
|
-
const name = normalized(model);
|
|
20
|
-
const family = Object.entries(MODEL_WINDOW_IDS).find(([key]) => name.includes(key));
|
|
21
|
-
return family === void 0 ? SHARED_WINDOW_IDS : [...SHARED_WINDOW_IDS, family[1]];
|
|
22
|
-
}
|
|
23
|
-
/** Pick the tightest quota window that constrains the given model. */
|
|
12
|
+
/** Pick the tightest API-reported quota window that applies to the model. */
|
|
24
13
|
function selectModelQuota(usage, model) {
|
|
25
|
-
const
|
|
26
|
-
const windows = Array.isArray(usage?.windows) ? usage.windows.filter((window) => ids.includes(window?.id) && isDisplayableWindow(window)) : [];
|
|
14
|
+
const windows = Array.isArray(usage?.windows) ? usage.windows.filter((window) => (window.appliesTo === "all" || window.appliesTo === model) && isDisplayableWindow(window)) : [];
|
|
27
15
|
if (windows.length === 0) return void 0;
|
|
28
16
|
const selected = windows.reduce((lowest, candidate) => candidate.remainingPercent < lowest.remainingPercent ? candidate : lowest);
|
|
29
17
|
return {
|
|
@@ -79,10 +67,6 @@ window.__ModuleLoader__.load({
|
|
|
79
67
|
usageEmpty: "此帳戶沒有回傳可顯示的額度視窗。請稍後重新整理;這不代表額度為零。",
|
|
80
68
|
usageUpdated: "更新於 {value}",
|
|
81
69
|
remaining: "剩餘 {value}%",
|
|
82
|
-
windowFiveHour: "5 小時額度",
|
|
83
|
-
windowSevenDay: "7 天額度",
|
|
84
|
-
windowSevenDayOpus: "Opus 7 天額度",
|
|
85
|
-
windowSevenDaySonnet: "Sonnet 7 天額度",
|
|
86
70
|
resets: "重置於 {value}",
|
|
87
71
|
resetUnknown: "重置時間未提供",
|
|
88
72
|
extraUsage: "額外用量",
|
|
@@ -118,10 +102,6 @@ window.__ModuleLoader__.load({
|
|
|
118
102
|
usageEmpty: "This account returned no displayable quota windows. Refresh later; this does not mean zero quota.",
|
|
119
103
|
usageUpdated: "Updated {value}",
|
|
120
104
|
remaining: "{value}% remaining",
|
|
121
|
-
windowFiveHour: "5-hour quota",
|
|
122
|
-
windowSevenDay: "7-day quota",
|
|
123
|
-
windowSevenDayOpus: "Opus 7-day quota",
|
|
124
|
-
windowSevenDaySonnet: "Sonnet 7-day quota",
|
|
125
105
|
resets: "Resets {value}",
|
|
126
106
|
resetUnknown: "Reset time not provided",
|
|
127
107
|
extraUsage: "Extra usage",
|
|
@@ -172,13 +152,14 @@ window.__ModuleLoader__.load({
|
|
|
172
152
|
};
|
|
173
153
|
const fill = (text, values) => Object.entries(values).reduce((next, [key, value]) => next.replace(`{${key}}`, String(value)), text);
|
|
174
154
|
const percent = (value) => Number(value).toLocaleString(void 0, { maximumFractionDigits: 1 });
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
155
|
+
const money = (value, currency, decimalPlaces) => new Intl.NumberFormat(void 0, {
|
|
156
|
+
style: "currency",
|
|
157
|
+
currency,
|
|
158
|
+
currencyDisplay: "code",
|
|
159
|
+
minimumFractionDigits: decimalPlaces,
|
|
160
|
+
maximumFractionDigits: decimalPlaces
|
|
161
|
+
}).format(value / 10 ** decimalPlaces);
|
|
162
|
+
const windowLabel = (window) => window.label ?? window.id;
|
|
182
163
|
const validDate = (value) => {
|
|
183
164
|
const date = new Date(value);
|
|
184
165
|
return Number.isFinite(date.getTime()) ? date : void 0;
|
|
@@ -539,9 +520,11 @@ window.__ModuleLoader__.load({
|
|
|
539
520
|
});
|
|
540
521
|
}
|
|
541
522
|
function ExtraUsageRow({ extraUsage, t }) {
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
523
|
+
const amount = (value) => money(value, extraUsage.currency, extraUsage.decimalPlaces);
|
|
524
|
+
const hasExactAmount = extraUsage.usedCredits !== void 0 && extraUsage.currency !== void 0 && extraUsage.decimalPlaces !== void 0;
|
|
525
|
+
const detail = !extraUsage.enabled ? t("extraUsageOff") : !hasExactAmount ? t("extraUsageUnknown") : extraUsage.monthlyLimit === void 0 ? fill(t("extraUsageUsedOnly"), { used: amount(extraUsage.usedCredits) }) : fill(t("extraUsageUsed"), {
|
|
526
|
+
used: amount(extraUsage.usedCredits),
|
|
527
|
+
limit: amount(extraUsage.monthlyLimit)
|
|
545
528
|
});
|
|
546
529
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
547
530
|
className: "claudeSubscriptionExtra",
|
|
@@ -640,13 +623,13 @@ window.__ModuleLoader__.load({
|
|
|
640
623
|
className: "claudeSubscriptionLimitTop",
|
|
641
624
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
642
625
|
className: "claudeSubscriptionLimitLabel",
|
|
643
|
-
children: windowLabel(window
|
|
626
|
+
children: windowLabel(window)
|
|
644
627
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("strong", { children: [percent(window.remainingPercent), "%"] })]
|
|
645
628
|
}),
|
|
646
629
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("progress", {
|
|
647
630
|
max: "100",
|
|
648
631
|
value: window.remainingPercent,
|
|
649
|
-
"aria-label": `${windowLabel(window
|
|
632
|
+
"aria-label": `${windowLabel(window)} ${fill(t("remaining"), { value: percent(window.remainingPercent) })}`
|
|
650
633
|
}),
|
|
651
634
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
652
635
|
className: "claudeSubscriptionLimitMeta",
|
|
@@ -665,6 +648,37 @@ window.__ModuleLoader__.load({
|
|
|
665
648
|
]
|
|
666
649
|
});
|
|
667
650
|
}
|
|
651
|
+
function ClaudeProviderAuth({ provider, rpc, t }) {
|
|
652
|
+
const [account, setAccount] = (0, react.useState)();
|
|
653
|
+
const [error, setError] = (0, react.useState)();
|
|
654
|
+
const isClaude = provider?.provider === PROVIDER;
|
|
655
|
+
(0, react.useEffect)(() => {
|
|
656
|
+
if (!isClaude) return;
|
|
657
|
+
let live = true;
|
|
658
|
+
rpc.call(CHANNEL, "status", {}).then(unwrap).then((next) => {
|
|
659
|
+
if (live) setAccount(next);
|
|
660
|
+
}).catch(() => {
|
|
661
|
+
if (live) setError(t("loadFailed"));
|
|
662
|
+
});
|
|
663
|
+
return () => {
|
|
664
|
+
live = false;
|
|
665
|
+
};
|
|
666
|
+
}, [isClaude, rpc, t]);
|
|
667
|
+
if (!isClaude) return null;
|
|
668
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, {
|
|
669
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountCard, {
|
|
670
|
+
rpc,
|
|
671
|
+
t,
|
|
672
|
+
account,
|
|
673
|
+
setAccount,
|
|
674
|
+
onSignedOut: () => void 0
|
|
675
|
+
}), error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
676
|
+
className: "claudeSubscriptionError",
|
|
677
|
+
role: "alert",
|
|
678
|
+
children: error
|
|
679
|
+
})]
|
|
680
|
+
});
|
|
681
|
+
}
|
|
668
682
|
function ClaudeSection({ preference, rpc, t }) {
|
|
669
683
|
const [account, setAccount] = (0, react.useState)();
|
|
670
684
|
const [error, setError] = (0, react.useState)();
|
|
@@ -746,6 +760,15 @@ window.__ModuleLoader__.load({
|
|
|
746
760
|
t
|
|
747
761
|
})
|
|
748
762
|
}, ClaudeSection));
|
|
763
|
+
ctx.slots.inject("settings.models.provider-card", () => ctx.slots.register({
|
|
764
|
+
name: "settings.models.provider-card",
|
|
765
|
+
key: "llm-pi-ai",
|
|
766
|
+
locale: NS,
|
|
767
|
+
inject: () => ({
|
|
768
|
+
rpc: connection.rpc,
|
|
769
|
+
t
|
|
770
|
+
})
|
|
771
|
+
}, ClaudeProviderAuth));
|
|
749
772
|
ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
|
|
750
773
|
name: "conversation.input.right",
|
|
751
774
|
id: "claude-subscription-quota",
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
2
|
-
import { LlmError } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
import { LlmError, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
3
3
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
4
|
-
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
5
4
|
import z from "@deepseek-ai/schemastery";
|
|
6
5
|
import { spawn } from "node:child_process";
|
|
6
|
+
import { access } from "node:fs/promises";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { resolve as resolvePath } from "node:path";
|
|
7
9
|
import { anthropicProvider as createAnthropicProvider } from "@earendil-works/pi-ai/providers/anthropic";
|
|
8
10
|
import { createModels } from "@earendil-works/pi-ai";
|
|
9
11
|
//#region src/credential-store.js
|
|
@@ -429,6 +431,7 @@ function createClaudeRpcHandler(coordinator, options = {}) {
|
|
|
429
431
|
*/
|
|
430
432
|
function anthropicSubscriptionProvider() {
|
|
431
433
|
const provider = createAnthropicProvider();
|
|
434
|
+
let models = Object.freeze([]);
|
|
432
435
|
const requestToken = Object.freeze({
|
|
433
436
|
name: "DSH-managed Anthropic OAuth request token",
|
|
434
437
|
async resolve({ credential }) {
|
|
@@ -445,10 +448,28 @@ function anthropicSubscriptionProvider() {
|
|
|
445
448
|
auth: Object.freeze({
|
|
446
449
|
...provider.auth,
|
|
447
450
|
apiKey: requestToken
|
|
448
|
-
})
|
|
451
|
+
}),
|
|
452
|
+
getModels: () => models,
|
|
453
|
+
setModels(value) {
|
|
454
|
+
models = Object.freeze([...value]);
|
|
455
|
+
}
|
|
449
456
|
});
|
|
450
457
|
}
|
|
451
|
-
const PI_AI_RUNTIME_VERSION = "0.
|
|
458
|
+
const PI_AI_RUNTIME_VERSION = "0.84.2";
|
|
459
|
+
const PI_AI_AUTH_CONTEXT = Object.freeze({
|
|
460
|
+
async env(name) {
|
|
461
|
+
return process.env[name];
|
|
462
|
+
},
|
|
463
|
+
async fileExists(path) {
|
|
464
|
+
const expanded = path === "~" || path.startsWith("~/") ? resolvePath(homedir(), path.slice(1).replace(/^\//, "")) : path;
|
|
465
|
+
try {
|
|
466
|
+
await access(expanded);
|
|
467
|
+
return true;
|
|
468
|
+
} catch {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
});
|
|
452
473
|
//#endregion
|
|
453
474
|
//#region src/settings-contract.js
|
|
454
475
|
const SETTINGS_NAMESPACE = "claude-subscription";
|
|
@@ -457,30 +478,95 @@ Object.freeze({ [QUICK_QUOTA_FIELD]: false });
|
|
|
457
478
|
//#endregion
|
|
458
479
|
//#region src/usage.js
|
|
459
480
|
const CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
481
|
+
const CLAUDE_MODELS_URL = "https://api.anthropic.com/v1/models?limit=1000";
|
|
460
482
|
const DEFAULT_TTL_MS = 6e4;
|
|
461
483
|
const DEFAULT_TIMEOUT_MS = 15e3;
|
|
462
484
|
const OAUTH_BETA = "oauth-2025-04-20";
|
|
463
|
-
const WINDOW_IDS = Object.freeze([
|
|
464
|
-
"five_hour",
|
|
465
|
-
"seven_day",
|
|
466
|
-
"seven_day_opus",
|
|
467
|
-
"seven_day_sonnet"
|
|
468
|
-
]);
|
|
469
485
|
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
486
|
+
function parseClaudeModels(value) {
|
|
487
|
+
if (!record(value) || !Array.isArray(value.data)) throw new Error("Claude returned a malformed model list");
|
|
488
|
+
return value.data.map((entry) => {
|
|
489
|
+
if (!record(entry) || typeof entry.id !== "string" || entry.id.length === 0 || typeof entry.display_name !== "string" || entry.display_name.length === 0) throw new Error("Claude returned a malformed model");
|
|
490
|
+
if (!Number.isSafeInteger(entry.max_input_tokens) || entry.max_input_tokens <= 0 || !Number.isSafeInteger(entry.max_tokens) || entry.max_tokens <= 0) throw new Error("Claude returned invalid model limits");
|
|
491
|
+
const capabilities = record(entry.capabilities) ? entry.capabilities : {};
|
|
492
|
+
return {
|
|
493
|
+
id: entry.id,
|
|
494
|
+
name: entry.display_name,
|
|
495
|
+
api: "anthropic-messages",
|
|
496
|
+
provider: "anthropic",
|
|
497
|
+
baseUrl: "https://api.anthropic.com",
|
|
498
|
+
reasoning: capabilities.thinking?.supported === true,
|
|
499
|
+
input: capabilities.image_input?.supported === true ? ["text", "image"] : ["text"],
|
|
500
|
+
cost: {
|
|
501
|
+
input: 0,
|
|
502
|
+
output: 0,
|
|
503
|
+
cacheRead: 0,
|
|
504
|
+
cacheWrite: 0
|
|
505
|
+
},
|
|
506
|
+
contextWindow: entry.max_input_tokens,
|
|
507
|
+
maxTokens: entry.max_tokens
|
|
508
|
+
};
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
function createClaudeModelReader(options) {
|
|
512
|
+
const getAuth = options.getAuth;
|
|
513
|
+
const setModels = options.setModels;
|
|
514
|
+
const fetchModels = options.fetch ?? fetch;
|
|
515
|
+
const now = options.now ?? Date.now;
|
|
516
|
+
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
517
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
518
|
+
let fetchedAt = 0;
|
|
519
|
+
let inFlight;
|
|
520
|
+
const load = async (signal) => {
|
|
521
|
+
const access = (await getAuth({ signal }))?.auth?.apiKey;
|
|
522
|
+
if (typeof access !== "string" || access.length === 0) throw new Error("Claude subscription is not signed in");
|
|
523
|
+
const response = await fetchModels(CLAUDE_MODELS_URL, {
|
|
524
|
+
method: "GET",
|
|
525
|
+
redirect: "error",
|
|
526
|
+
headers: {
|
|
527
|
+
authorization: `Bearer ${access}`,
|
|
528
|
+
"anthropic-beta": OAUTH_BETA,
|
|
529
|
+
"anthropic-version": "2023-06-01",
|
|
530
|
+
accept: "application/json",
|
|
531
|
+
"cache-control": "no-store"
|
|
532
|
+
},
|
|
533
|
+
signal: requestSignal(signal, timeoutMs)
|
|
534
|
+
});
|
|
535
|
+
if (!response.ok) throw new Error(`Claude model request failed (HTTP ${response.status})`);
|
|
536
|
+
const models = parseClaudeModels(await response.json());
|
|
537
|
+
setModels(models);
|
|
538
|
+
fetchedAt = now();
|
|
539
|
+
return models;
|
|
540
|
+
};
|
|
541
|
+
return Object.freeze({
|
|
542
|
+
read({ force = false, signal } = {}) {
|
|
543
|
+
if (!force && fetchedAt !== 0 && now() - fetchedAt < ttlMs) return Promise.resolve();
|
|
544
|
+
if (inFlight !== void 0) return inFlight;
|
|
545
|
+
const current = load(signal).finally(() => {
|
|
546
|
+
if (inFlight === current) inFlight = void 0;
|
|
547
|
+
});
|
|
548
|
+
inFlight = current;
|
|
549
|
+
return current;
|
|
550
|
+
},
|
|
551
|
+
clear() {
|
|
552
|
+
fetchedAt = 0;
|
|
553
|
+
setModels([]);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
470
557
|
function epochSecondsOf(value) {
|
|
471
558
|
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
472
559
|
const parsed = Date.parse(value);
|
|
473
560
|
return Number.isFinite(parsed) ? Math.floor(parsed / 1e3) : void 0;
|
|
474
561
|
}
|
|
475
|
-
function windowOf(id,
|
|
476
|
-
if (value === void 0 || value === null) return void 0;
|
|
477
|
-
if (!record(value)) throw new Error("Claude returned a malformed usage window");
|
|
478
|
-
const utilization = value.utilization;
|
|
562
|
+
function windowOf(id, label, utilization, resetsAtValue, appliesTo) {
|
|
479
563
|
if (!Number.isFinite(utilization) || utilization < 0 || utilization > 100) throw new Error("Claude returned an invalid utilization percentage");
|
|
480
|
-
const resetsAt = epochSecondsOf(
|
|
564
|
+
const resetsAt = epochSecondsOf(resetsAtValue);
|
|
481
565
|
return {
|
|
482
566
|
id,
|
|
567
|
+
label,
|
|
483
568
|
remainingPercent: 100 - utilization,
|
|
569
|
+
...appliesTo === void 0 ? {} : { appliesTo },
|
|
484
570
|
...resetsAt === void 0 ? {} : { resetsAt }
|
|
485
571
|
};
|
|
486
572
|
}
|
|
@@ -489,15 +575,40 @@ function decimalOf(value, label) {
|
|
|
489
575
|
if (!Number.isFinite(value) || value < 0) throw new Error(`Claude returned an invalid ${label}`);
|
|
490
576
|
return value;
|
|
491
577
|
}
|
|
492
|
-
function extraUsageOf(value) {
|
|
578
|
+
function extraUsageOf(value, spend) {
|
|
579
|
+
if (record(spend) && typeof spend.enabled === "boolean") {
|
|
580
|
+
const used = record(spend.used) ? spend.used : void 0;
|
|
581
|
+
const limit = record(spend.limit) ? spend.limit : void 0;
|
|
582
|
+
const usedCredits = decimalOf(used?.amount_minor, "used credit amount");
|
|
583
|
+
const monthlyLimit = decimalOf(limit?.amount_minor, "monthly credit limit");
|
|
584
|
+
const currency = used?.currency ?? limit?.currency;
|
|
585
|
+
const decimalPlaces = used?.exponent ?? limit?.exponent;
|
|
586
|
+
if (currency !== void 0 && (typeof currency !== "string" || !/^[A-Za-z]{3}$/.test(currency))) throw new Error("Claude returned an invalid extra usage currency");
|
|
587
|
+
if (decimalPlaces !== void 0 && (!Number.isSafeInteger(decimalPlaces) || decimalPlaces < 0 || decimalPlaces > 20)) throw new Error("Claude returned invalid extra usage decimal places");
|
|
588
|
+
if (typeof used?.currency === "string" && typeof limit?.currency === "string" && used.currency.toUpperCase() !== limit.currency.toUpperCase() || Number.isSafeInteger(used?.exponent) && Number.isSafeInteger(limit?.exponent) && used.exponent !== limit.exponent) throw new Error("Claude returned inconsistent extra usage units");
|
|
589
|
+
return {
|
|
590
|
+
enabled: spend.enabled,
|
|
591
|
+
...currency === void 0 ? {} : { currency: currency.toUpperCase() },
|
|
592
|
+
...decimalPlaces === void 0 ? {} : { decimalPlaces },
|
|
593
|
+
...monthlyLimit === void 0 ? {} : { monthlyLimit },
|
|
594
|
+
...usedCredits === void 0 ? {} : { usedCredits },
|
|
595
|
+
...Number.isFinite(spend.percent) ? { utilization: spend.percent } : {}
|
|
596
|
+
};
|
|
597
|
+
}
|
|
493
598
|
if (value === void 0 || value === null) return void 0;
|
|
494
599
|
if (!record(value)) throw new Error("Claude returned malformed extra usage details");
|
|
495
600
|
if (typeof value.is_enabled !== "boolean") return void 0;
|
|
496
601
|
const monthlyLimit = decimalOf(value.monthly_limit, "monthly credit limit");
|
|
497
602
|
const usedCredits = decimalOf(value.used_credits, "used credit amount");
|
|
498
603
|
const utilization = decimalOf(value.utilization, "extra usage utilization");
|
|
604
|
+
const currency = value.currency;
|
|
605
|
+
const decimalPlaces = value.decimal_places;
|
|
606
|
+
if (currency !== void 0 && currency !== null && (typeof currency !== "string" || !/^[A-Za-z]{3}$/.test(currency))) throw new Error("Claude returned an invalid extra usage currency");
|
|
607
|
+
if (decimalPlaces !== void 0 && decimalPlaces !== null && (!Number.isSafeInteger(decimalPlaces) || decimalPlaces < 0 || decimalPlaces > 20)) throw new Error("Claude returned invalid extra usage decimal places");
|
|
499
608
|
return {
|
|
500
609
|
enabled: value.is_enabled,
|
|
610
|
+
...typeof currency === "string" ? { currency: currency.toUpperCase() } : {},
|
|
611
|
+
...Number.isSafeInteger(decimalPlaces) ? { decimalPlaces } : {},
|
|
501
612
|
...monthlyLimit === void 0 ? {} : { monthlyLimit },
|
|
502
613
|
...usedCredits === void 0 ? {} : { usedCredits },
|
|
503
614
|
...utilization === void 0 ? {} : { utilization }
|
|
@@ -506,8 +617,16 @@ function extraUsageOf(value) {
|
|
|
506
617
|
/** Reduce the provider payload to a browser-safe quota projection. */
|
|
507
618
|
function parseClaudeUsage(value) {
|
|
508
619
|
if (!record(value)) throw new Error("Claude returned a malformed usage response");
|
|
509
|
-
const windows =
|
|
510
|
-
|
|
620
|
+
const windows = Array.isArray(value.limits) ? value.limits.map((entry, index) => {
|
|
621
|
+
if (!record(entry) || typeof entry.kind !== "string" || entry.kind.length === 0) throw new Error("Claude returned a malformed usage window");
|
|
622
|
+
const model = record(entry.scope) && record(entry.scope.model) ? entry.scope.model : void 0;
|
|
623
|
+
const modelName = typeof model?.display_name === "string" && model.display_name.length > 0 ? model.display_name : void 0;
|
|
624
|
+
const group = typeof entry.group === "string" && entry.group.length > 0 ? entry.group : entry.kind;
|
|
625
|
+
const label = modelName === void 0 ? group : `${group} (${modelName})`;
|
|
626
|
+
const appliesTo = model === void 0 ? "all" : typeof model.id === "string" && model.id.length > 0 ? model.id : void 0;
|
|
627
|
+
return windowOf(`${entry.kind}:${index}`, label, entry.percent, entry.resets_at, appliesTo);
|
|
628
|
+
}) : Object.entries(value).filter(([, entry]) => record(entry) && Object.hasOwn(entry, "utilization") && Object.hasOwn(entry, "resets_at")).map(([id, entry]) => windowOf(id, id, entry.utilization, entry.resets_at));
|
|
629
|
+
const extraUsage = extraUsageOf(value.extra_usage, value.spend);
|
|
511
630
|
return {
|
|
512
631
|
windows,
|
|
513
632
|
...extraUsage === void 0 ? {} : { extraUsage }
|
|
@@ -533,7 +652,7 @@ function createClaudeUsageReader(options) {
|
|
|
533
652
|
const now = options.now ?? Date.now;
|
|
534
653
|
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
535
654
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
536
|
-
const userAgent = options.userAgent ?? "dsh-claude-subscription/0.
|
|
655
|
+
const userAgent = options.userAgent ?? "dsh-claude-subscription/0.3.1";
|
|
537
656
|
let cached;
|
|
538
657
|
let inFlight;
|
|
539
658
|
const load = async (signal) => {
|
|
@@ -583,22 +702,10 @@ function createClaudeUsageReader(options) {
|
|
|
583
702
|
}
|
|
584
703
|
//#endregion
|
|
585
704
|
//#region src/composer-quota.js
|
|
586
|
-
const SHARED_WINDOW_IDS = Object.freeze(["five_hour", "seven_day"]);
|
|
587
|
-
const MODEL_WINDOW_IDS = Object.freeze({
|
|
588
|
-
opus: "seven_day_opus",
|
|
589
|
-
sonnet: "seven_day_sonnet"
|
|
590
|
-
});
|
|
591
705
|
const isDisplayableWindow = (window) => Number.isFinite(window?.remainingPercent) && window.remainingPercent >= 0 && window.remainingPercent <= 100;
|
|
592
|
-
|
|
593
|
-
function applicableIds(model) {
|
|
594
|
-
const name = normalized(model);
|
|
595
|
-
const family = Object.entries(MODEL_WINDOW_IDS).find(([key]) => name.includes(key));
|
|
596
|
-
return family === void 0 ? SHARED_WINDOW_IDS : [...SHARED_WINDOW_IDS, family[1]];
|
|
597
|
-
}
|
|
598
|
-
/** Pick the tightest quota window that constrains the given model. */
|
|
706
|
+
/** Pick the tightest API-reported quota window that applies to the model. */
|
|
599
707
|
function selectModelQuota(usage, model) {
|
|
600
|
-
const
|
|
601
|
-
const windows = Array.isArray(usage?.windows) ? usage.windows.filter((window) => ids.includes(window?.id) && isDisplayableWindow(window)) : [];
|
|
708
|
+
const windows = Array.isArray(usage?.windows) ? usage.windows.filter((window) => (window.appliesTo === "all" || window.appliesTo === model) && isDisplayableWindow(window)) : [];
|
|
602
709
|
if (windows.length === 0) return void 0;
|
|
603
710
|
const selected = windows.reduce((lowest, candidate) => candidate.remainingPercent < lowest.remainingPercent ? candidate : lowest);
|
|
604
711
|
return {
|
|
@@ -608,6 +715,28 @@ function selectModelQuota(usage, model) {
|
|
|
608
715
|
};
|
|
609
716
|
}
|
|
610
717
|
//#endregion
|
|
718
|
+
class ClaudeApiAdapter extends PiAiAdapter {
|
|
719
|
+
constructor(options, modelReader) {
|
|
720
|
+
super(options);
|
|
721
|
+
this.modelReader = modelReader;
|
|
722
|
+
}
|
|
723
|
+
async listModels(provider) {
|
|
724
|
+
await this.modelReader.read();
|
|
725
|
+
return super.listModels(provider);
|
|
726
|
+
}
|
|
727
|
+
async resolveModel(provider, model, signal) {
|
|
728
|
+
await this.modelReader.read({ signal });
|
|
729
|
+
return super.resolveModel(provider, model, signal);
|
|
730
|
+
}
|
|
731
|
+
async prepareCall(provider, model, signal) {
|
|
732
|
+
await this.modelReader.read({ signal });
|
|
733
|
+
return super.prepareCall(provider, model, signal);
|
|
734
|
+
}
|
|
735
|
+
async *stream(options) {
|
|
736
|
+
await this.modelReader.read({ signal: options.signal });
|
|
737
|
+
yield* super.stream(options);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
611
740
|
//#region src/index.js
|
|
612
741
|
const name = "claude-subscription";
|
|
613
742
|
const inject = [
|
|
@@ -636,7 +765,7 @@ const KNOWN_USAGE_ERRORS = /* @__PURE__ */ new Set([
|
|
|
636
765
|
* Extend the account RPC with the preference toggle and the quota projection.
|
|
637
766
|
* Unknown endpoints stay owned by the login coordinator.
|
|
638
767
|
*/
|
|
639
|
-
function createSubscriptionRpcHandler({ authHandler, usageReader, preferences }) {
|
|
768
|
+
function createSubscriptionRpcHandler({ authHandler, usageReader, modelReader, preferences }) {
|
|
640
769
|
return async (endpoint, payload, signal) => {
|
|
641
770
|
if (endpoint === "preferences/status" || endpoint === "preferences/update") try {
|
|
642
771
|
signal.throwIfAborted();
|
|
@@ -668,22 +797,36 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, preferences })
|
|
|
668
797
|
return publicError("internal", message);
|
|
669
798
|
}
|
|
670
799
|
const result = await authHandler(endpoint, payload, signal);
|
|
671
|
-
if (endpoint === "logout" && result.ok === true)
|
|
800
|
+
if (endpoint === "logout" && result.ok === true) {
|
|
801
|
+
usageReader.clear();
|
|
802
|
+
modelReader?.clear();
|
|
803
|
+
}
|
|
672
804
|
return result;
|
|
673
805
|
};
|
|
674
806
|
}
|
|
675
807
|
function apply(ctx) {
|
|
676
|
-
const
|
|
808
|
+
const defaultPreferences = Object.freeze({ [QUICK_QUOTA_FIELD]: false });
|
|
809
|
+
let preferenceSource = () => defaultPreferences;
|
|
810
|
+
ctx.settings.installSection(ctx, SETTINGS_NAMESPACE, z.object({ [QUICK_QUOTA_FIELD]: z.boolean().default(false) }), defaultPreferences, {
|
|
811
|
+
setSource: (current) => {
|
|
812
|
+
preferenceSource = current;
|
|
813
|
+
},
|
|
814
|
+
onChange: () => void 0
|
|
815
|
+
});
|
|
677
816
|
const preferences = {
|
|
678
817
|
status: () => ({
|
|
679
|
-
[QUICK_QUOTA_FIELD]:
|
|
818
|
+
[QUICK_QUOTA_FIELD]: preferenceSource()[QUICK_QUOTA_FIELD],
|
|
680
819
|
writable: ctx.settings.writable
|
|
681
820
|
}),
|
|
682
|
-
update: (patch) =>
|
|
821
|
+
update: async (patch) => {
|
|
822
|
+
await ctx.settings.update(SETTINGS_NAMESPACE, patch);
|
|
823
|
+
return preferenceSource();
|
|
824
|
+
}
|
|
683
825
|
};
|
|
684
826
|
const store = new DshOAuthCredentialStore(ctx.credentials, CREDENTIAL_REF);
|
|
685
827
|
const provider = anthropicSubscriptionProvider();
|
|
686
|
-
const
|
|
828
|
+
const auth = Object.freeze({ credentials: store, authContext: PI_AI_AUTH_CONTEXT });
|
|
829
|
+
const authModels = createModels(auth);
|
|
687
830
|
authModels.setProvider(provider);
|
|
688
831
|
const profile = Object.freeze({
|
|
689
832
|
provider: PROVIDER,
|
|
@@ -691,13 +834,22 @@ function apply(ctx) {
|
|
|
691
834
|
piProvider: provider,
|
|
692
835
|
configuredMaxTokens: /* @__PURE__ */ new Map(),
|
|
693
836
|
streamIdleTimeoutMs: 600 * 1e3,
|
|
837
|
+
maxRequestImageBytes: 20 * 1024 * 1024,
|
|
838
|
+
requestImagePixelBudget: 2048 * 2048,
|
|
839
|
+
requestImageMaxBytes: 1024 * 1024,
|
|
840
|
+
retryPolicy: resolveRetryPolicy(void 0, "claude-subscription retryPolicy"),
|
|
694
841
|
cacheRetention: "short",
|
|
695
842
|
transport: "sse"
|
|
696
843
|
});
|
|
697
844
|
const profiles = /* @__PURE__ */ new Map([[PROVIDER, profile]]);
|
|
698
845
|
const resolveAuth = () => authModels.getAuth(PROVIDER);
|
|
699
|
-
const
|
|
846
|
+
const modelReader = createClaudeModelReader({
|
|
847
|
+
getAuth: resolveAuth,
|
|
848
|
+
setModels: (models) => provider.setModels(models)
|
|
849
|
+
});
|
|
850
|
+
const adapter = new ClaudeApiAdapter({
|
|
700
851
|
profiles: () => profiles,
|
|
852
|
+
auth,
|
|
701
853
|
resolveApiKey: async () => {
|
|
702
854
|
let resolved;
|
|
703
855
|
try {
|
|
@@ -709,16 +861,17 @@ function apply(ctx) {
|
|
|
709
861
|
return resolved.auth.apiKey;
|
|
710
862
|
},
|
|
711
863
|
resolveAttachments: () => void 0
|
|
712
|
-
});
|
|
864
|
+
}, modelReader);
|
|
713
865
|
ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
714
866
|
const coordinator = new ClaudeLoginCoordinator(createClaudeAuthService(authModels, store));
|
|
715
867
|
const usageReader = createClaudeUsageReader({ getAuth: resolveAuth });
|
|
716
868
|
const handler = createSubscriptionRpcHandler({
|
|
717
869
|
authHandler: createClaudeRpcHandler(coordinator, { openExternal: openClaudeAuthUrl }),
|
|
718
870
|
usageReader,
|
|
871
|
+
modelReader,
|
|
719
872
|
preferences
|
|
720
873
|
});
|
|
721
874
|
ctx.effect(() => ctx.connection.rpc.handle(CHANNEL, handler, { authority: "loopback" }), "claude-subscription: loopback account RPC");
|
|
722
875
|
}
|
|
723
876
|
//#endregion
|
|
724
|
-
export { CLAUDE_USAGE_URL, ClaudeLoginCoordinator, DshOAuthCredentialStore, PI_AI_RUNTIME_VERSION, anthropicSubscriptionProvider, apply, assertClaudeAuthUrl, createClaudeAuthService, createClaudeRpcHandler, createClaudeUsageReader, createSubscriptionRpcHandler, inject, name, openClaudeAuthUrl, parseClaudeUsage, selectModelQuota };
|
|
877
|
+
export { CLAUDE_MODELS_URL, CLAUDE_USAGE_URL, ClaudeLoginCoordinator, DshOAuthCredentialStore, PI_AI_RUNTIME_VERSION, anthropicSubscriptionProvider, apply, assertClaudeAuthUrl, createClaudeAuthService, createClaudeModelReader, createClaudeRpcHandler, createClaudeUsageReader, createSubscriptionRpcHandler, inject, name, openClaudeAuthUrl, parseClaudeModels, parseClaudeUsage, selectModelQuota };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-claude-subscription",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"packageManager": "pnpm@11.19.0",
|
|
5
5
|
"description": "Claude Pro/Max subscription for DeepSeek Harness with official Anthropic OAuth login and automatic token refresh",
|
|
6
6
|
"type": "module",
|
|
@@ -18,8 +18,6 @@
|
|
|
18
18
|
"README.en.md",
|
|
19
19
|
"AGENTS.md",
|
|
20
20
|
"LICENSE",
|
|
21
|
-
"SECURITY.md",
|
|
22
|
-
"THIRD_PARTY_NOTICES.md",
|
|
23
21
|
"docs/assets/*.png"
|
|
24
22
|
],
|
|
25
23
|
"dsh": {
|
|
@@ -31,17 +29,17 @@
|
|
|
31
29
|
"@deepseek-ai/dsh-api-remotes",
|
|
32
30
|
"@deepseek-ai/dsh-client-connection",
|
|
33
31
|
"@deepseek-ai/dsh-client-locale",
|
|
34
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
35
32
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
36
33
|
"@deepseek-ai/dsh-client-ui-model-selection",
|
|
37
34
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
35
|
+
"@deepseek-ai/dsh-client-ui-settings-models",
|
|
38
36
|
"@deepseek-ai/dsh-client-ui-tool"
|
|
39
37
|
],
|
|
40
38
|
"platform": "web"
|
|
41
39
|
}
|
|
42
40
|
},
|
|
43
41
|
"scripts": {
|
|
44
|
-
"build": "
|
|
42
|
+
"build": "node --check lib/index.js && node --check lib/client.js",
|
|
45
43
|
"test": "node --test tests/*.test.mjs",
|
|
46
44
|
"check": "pnpm run test && pnpm run build && pnpm pack --pack-destination .artifacts"
|
|
47
45
|
},
|
|
@@ -65,25 +63,21 @@
|
|
|
65
63
|
"node": "^22.19.0 || >=24.0.0"
|
|
66
64
|
},
|
|
67
65
|
"peerDependencies": {
|
|
68
|
-
"@deepseek-ai/
|
|
69
|
-
"@deepseek-ai/dsh-
|
|
70
|
-
"@deepseek-ai/dsh-
|
|
71
|
-
"@deepseek-ai/dsh-
|
|
72
|
-
"@deepseek-ai/
|
|
73
|
-
"@
|
|
74
|
-
"@deepseek-ai/dsh-client-ui-model-selection": "0.1.0-rc.6 || 0.1.0-rc.7",
|
|
75
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.6 || 0.1.0-rc.7",
|
|
76
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.6 || 0.1.0-rc.7",
|
|
77
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.1.0-rc.6 || 0.1.0-rc.7",
|
|
78
|
-
"@deepseek-ai/dsh-credentials": "0.1.0-rc.6 || 0.1.0-rc.7",
|
|
79
|
-
"@deepseek-ai/dsh-llm": "0.1.0-rc.6 || 0.1.0-rc.7",
|
|
80
|
-
"@deepseek-ai/dsh-llm-pi-ai": "0.1.0-rc.6 || 0.1.0-rc.7",
|
|
81
|
-
"@deepseek-ai/dsh-settings": "0.1.0-rc.6 || 0.1.0-rc.7",
|
|
82
|
-
"@deepseek-ai/schemastery": "3.18.1",
|
|
83
|
-
"@earendil-works/pi-ai": "0.82.1",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-alpha.5",
|
|
67
|
+
"@deepseek-ai/dsh-credentials": "0.1.2-alpha.5",
|
|
68
|
+
"@deepseek-ai/dsh-llm": "0.1.2-alpha.5",
|
|
69
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.1.2-alpha.5",
|
|
70
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
71
|
+
"@earendil-works/pi-ai": "^0.84.2",
|
|
84
72
|
"react": "^18.2.0"
|
|
85
73
|
},
|
|
86
|
-
"
|
|
87
|
-
"
|
|
74
|
+
"peerDependenciesMeta": {
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-primitives": { "optional": true },
|
|
76
|
+
"@deepseek-ai/dsh-credentials": { "optional": true },
|
|
77
|
+
"@deepseek-ai/dsh-llm": { "optional": true },
|
|
78
|
+
"@deepseek-ai/dsh-llm-pi-ai": { "optional": true },
|
|
79
|
+
"@deepseek-ai/schemastery": { "optional": true },
|
|
80
|
+
"@earendil-works/pi-ai": { "optional": true },
|
|
81
|
+
"react": { "optional": true }
|
|
88
82
|
}
|
|
89
83
|
}
|
package/SECURITY.md
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# Security policy
|
|
2
|
-
|
|
3
|
-
## Supported version
|
|
4
|
-
|
|
5
|
-
Security fixes are applied to the latest tagged release. DeepSeek Harness is a developer preview, so compatibility fixes may require upgrading both DSH and this bundle.
|
|
6
|
-
|
|
7
|
-
## Reporting a vulnerability
|
|
8
|
-
|
|
9
|
-
Use a private GitHub security advisory at:
|
|
10
|
-
|
|
11
|
-
https://github.com/x5427876/dsh-claude-subscription/security/advisories/new
|
|
12
|
-
|
|
13
|
-
Do not open a public issue containing access tokens, refresh tokens, account identifiers, callback URLs with authorization codes, raw DSH credential data, or private prompts. Include the affected version, operating system, DSH version, a minimal reproduction, and impact. Replace all credentials with unmistakably fake placeholders.
|
|
14
|
-
|
|
15
|
-
## Trust boundary
|
|
16
|
-
|
|
17
|
-
- The plugin runs with the privileges of the DSH host process.
|
|
18
|
-
- OAuth credentials remain in DSH's host credential service.
|
|
19
|
-
- The browser settings client can call only a loopback-authorized, redacted RPC surface.
|
|
20
|
-
- Login links are restricted to the HTTPS `claude.ai` origin before they are returned or opened.
|
|
21
|
-
- Token refresh runs host-side through pi-ai's Anthropic OAuth flow; tokens never enter the browser client.
|
|
22
|
-
- The bundle defines no API-key or cross-provider fallback.
|
|
23
|
-
|
|
24
|
-
Installing any DSH plugin grants its host code access to the services named in its composition. Review the tag or commit you install. Prefer a tagged version or a full commit hash over a moving branch.
|
|
25
|
-
|
|
26
|
-
## Out of scope
|
|
27
|
-
|
|
28
|
-
- availability or policy changes in Claude, claude.ai, Anthropic OAuth, or DeepSeek Harness
|
|
29
|
-
- compromised host machines or compromised DSH installations
|
|
30
|
-
- secrets deliberately pasted into prompts, logs, issues, or screenshots
|
|
31
|
-
- provider billing or quota disputes
|
package/THIRD_PARTY_NOTICES.md
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Third-party notices
|
|
2
|
-
|
|
3
|
-
This project depends on software distributed under its own terms. The dependency lockfile records the exact resolved graph used for the release.
|
|
4
|
-
|
|
5
|
-
| Project | Role | License | Source |
|
|
6
|
-
| --- | --- | --- | --- |
|
|
7
|
-
| DeepSeek Harness packages | Plugin host, client slots, credential and LLM interfaces | MIT | https://github.com/deepseek-ai/deepseek-harness |
|
|
8
|
-
| `@earendil-works/pi-ai` 0.82.1 | Anthropic OAuth (Claude Pro/Max), model catalog, and Messages transport | MIT | https://github.com/earendil-works/pi |
|
|
9
|
-
| React | DSH settings component runtime | MIT | https://github.com/facebook/react |
|
|
10
|
-
| tsdown | Development-time bundler | MIT | https://github.com/rolldown/tsdown |
|
|
11
|
-
|
|
12
|
-
No third-party project endorses this community plugin. See each installed package for its complete license text and transitive dependency notices.
|