@jxgame2020/dsh-token-quota 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +95 -88
- package/lib/index.js +101 -57
- package/lib/types/bound-test.d.ts +2 -0
- package/lib/types/bound-test.js +5 -0
- package/lib/types/client/TokenQuotaPanel.d.ts +1 -1
- package/lib/types/client/bound-test.d.ts +2 -0
- package/lib/types/client/bound-test.js +7 -0
- package/lib/types/client/import-test.d.ts +2 -0
- package/lib/types/client/import-test.js +3 -0
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/index.js +28 -14
- package/lib/types/client/store.d.ts +2 -2
- package/lib/types/client/store.js +1 -1
- package/lib/types/import-test.d.ts +2 -0
- package/lib/types/import-test.js +3 -0
- package/lib/types/index.js +115 -66
- package/package.json +14 -10
package/lib/client.js
CHANGED
|
@@ -4,7 +4,7 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
-
let
|
|
7
|
+
let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
9
|
let react = require("react");
|
|
10
10
|
//#region lib/types/client/store.js
|
|
@@ -26,7 +26,7 @@ window.__ModuleLoader__.load({
|
|
|
26
26
|
* @returns the store handle.
|
|
27
27
|
*/
|
|
28
28
|
function createTokenQuotaPanelStore() {
|
|
29
|
-
return (0,
|
|
29
|
+
return (0, _deepseek_ai_dsh_client_store.defineStore)({
|
|
30
30
|
init: () => ({
|
|
31
31
|
snapshot: null,
|
|
32
32
|
groups: [],
|
|
@@ -79,7 +79,7 @@ window.__ModuleLoader__.load({
|
|
|
79
79
|
d.dimWhenIdle = dimWhenIdle;
|
|
80
80
|
},
|
|
81
81
|
setUpgrade: (d, upgrade) => {
|
|
82
|
-
const normalized = upgrade !== null && upgrade.latestVersion !== "" && upgrade.latestVersion !== "0.1.
|
|
82
|
+
const normalized = upgrade !== null && upgrade.latestVersion !== "" && upgrade.latestVersion !== "0.1.19" ? upgrade : null;
|
|
83
83
|
if (d.upgrade === null || d.upgrade.latestVersion !== normalized?.latestVersion) d.upgradeDismissed = false;
|
|
84
84
|
d.upgrade = normalized;
|
|
85
85
|
},
|
|
@@ -185,91 +185,91 @@ window.__ModuleLoader__.load({
|
|
|
185
185
|
document.head.appendChild(tag);
|
|
186
186
|
}
|
|
187
187
|
var TokenQuotaPanel_module_css_default = {
|
|
188
|
-
"
|
|
188
|
+
"panel": "NPLLIq_panel",
|
|
189
|
+
"rowHeader": "NPLLIq_rowHeader",
|
|
190
|
+
"upgradeCommandRow": "NPLLIq_upgradeCommandRow",
|
|
189
191
|
"logToolbar": "NPLLIq_logToolbar",
|
|
190
|
-
"radioRow": "NPLLIq_radioRow",
|
|
191
|
-
"dialogClose": "NPLLIq_dialogClose",
|
|
192
192
|
"gearBtn": "NPLLIq_gearBtn",
|
|
193
|
-
"
|
|
194
|
-
"
|
|
193
|
+
"logPagerInfo": "NPLLIq_logPagerInfo",
|
|
194
|
+
"fillWarn": "NPLLIq_fillWarn",
|
|
195
|
+
"headerText": "NPLLIq_headerText",
|
|
196
|
+
"dialogLabel": "NPLLIq_dialogLabel",
|
|
197
|
+
"dialogBtn": "NPLLIq_dialogBtn",
|
|
195
198
|
"monitorHint": "NPLLIq_monitorHint",
|
|
196
|
-
"
|
|
199
|
+
"logPagerPages": "NPLLIq_logPagerPages",
|
|
200
|
+
"subtitle": "NPLLIq_subtitle",
|
|
201
|
+
"upgradeCommands": "NPLLIq_upgradeCommands",
|
|
202
|
+
"dialogActions": "NPLLIq_dialogActions",
|
|
203
|
+
"title": "NPLLIq_title",
|
|
204
|
+
"fillOver": "NPLLIq_fillOver",
|
|
205
|
+
"monitorRow": "NPLLIq_monitorRow",
|
|
206
|
+
"panelDimStrong": "NPLLIq_panelDimStrong",
|
|
197
207
|
"currentBadge": "NPLLIq_currentBadge",
|
|
208
|
+
"checkUpdatesRow": "NPLLIq_checkUpdatesRow",
|
|
209
|
+
"input": "NPLLIq_input",
|
|
210
|
+
"checkErrorHint": "NPLLIq_checkErrorHint",
|
|
211
|
+
"dialogOverlay": "NPLLIq_dialogOverlay",
|
|
198
212
|
"tabLabel": "NPLLIq_tabLabel",
|
|
199
|
-
"
|
|
200
|
-
"
|
|
201
|
-
"
|
|
213
|
+
"upgradeClose": "NPLLIq_upgradeClose",
|
|
214
|
+
"dialogClose": "NPLLIq_dialogClose",
|
|
215
|
+
"logScroll": "NPLLIq_logScroll",
|
|
216
|
+
"fullNotice": "NPLLIq_fullNotice",
|
|
202
217
|
"settingsBtn": "NPLLIq_settingsBtn",
|
|
203
|
-
"
|
|
218
|
+
"upgradeBanner": "NPLLIq_upgradeBanner",
|
|
219
|
+
"upgradeBannerFlash": "NPLLIq_upgradeBannerFlash",
|
|
220
|
+
"logPager": "NPLLIq_logPager",
|
|
204
221
|
"logPageSizeSelect": "NPLLIq_logPageSizeSelect",
|
|
205
222
|
"rowName": "NPLLIq_rowName",
|
|
206
|
-
"
|
|
207
|
-
"
|
|
208
|
-
"
|
|
209
|
-
"panel": "NPLLIq_panel",
|
|
210
|
-
"copyBtn": "NPLLIq_copyBtn",
|
|
211
|
-
"dialogLabel": "NPLLIq_dialogLabel",
|
|
212
|
-
"upgradeCommand": "NPLLIq_upgradeCommand",
|
|
213
|
-
"logSearch": "NPLLIq_logSearch",
|
|
214
|
-
"version": "NPLLIq_version",
|
|
215
|
-
"logDialog": "NPLLIq_logDialog",
|
|
216
|
-
"rowMeta": "NPLLIq_rowMeta",
|
|
217
|
-
"selectBtn": "NPLLIq_selectBtn",
|
|
218
|
-
"dialogOverlay": "NPLLIq_dialogOverlay",
|
|
223
|
+
"logClearDate": "NPLLIq_logClearDate",
|
|
224
|
+
"collapse": "NPLLIq_collapse",
|
|
225
|
+
"bar": "NPLLIq_bar",
|
|
219
226
|
"controls": "NPLLIq_controls",
|
|
220
|
-
"fullNotice": "NPLLIq_fullNotice",
|
|
221
|
-
"upgradeBannerFlash": "NPLLIq_upgradeBannerFlash",
|
|
222
|
-
"dialog": "NPLLIq_dialog",
|
|
223
|
-
"upgradeCommands": "NPLLIq_upgradeCommands",
|
|
224
227
|
"copyBtnDone": "NPLLIq_copyBtnDone",
|
|
225
|
-
"
|
|
226
|
-
"
|
|
227
|
-
"
|
|
228
|
+
"body": "NPLLIq_body",
|
|
229
|
+
"logPagerBtn": "NPLLIq_logPagerBtn",
|
|
230
|
+
"notice": "NPLLIq_notice",
|
|
228
231
|
"monitorName": "NPLLIq_monitorName",
|
|
229
|
-
"
|
|
230
|
-
"
|
|
231
|
-
"panelDimStrong": "NPLLIq_panelDimStrong",
|
|
232
|
-
"bar": "NPLLIq_bar",
|
|
233
|
-
"save": "NPLLIq_save",
|
|
234
|
-
"upgradeClose": "NPLLIq_upgradeClose",
|
|
235
|
-
"dialogHeader": "NPLLIq_dialogHeader",
|
|
236
|
-
"upgradeBanner": "NPLLIq_upgradeBanner",
|
|
232
|
+
"logClearBtn": "NPLLIq_logClearBtn",
|
|
233
|
+
"dialogBody": "NPLLIq_dialogBody",
|
|
237
234
|
"resetRow": "NPLLIq_resetRow",
|
|
238
|
-
"
|
|
239
|
-
"
|
|
235
|
+
"dialogHint": "NPLLIq_dialogHint",
|
|
236
|
+
"panelDim": "NPLLIq_panelDim",
|
|
237
|
+
"logDialog": "NPLLIq_logDialog",
|
|
238
|
+
"tab": "NPLLIq_tab",
|
|
239
|
+
"upgradeHint": "NPLLIq_upgradeHint",
|
|
240
|
+
"header": "NPLLIq_header",
|
|
240
241
|
"switch": "NPLLIq_switch",
|
|
241
|
-
"
|
|
242
|
+
"noticeError": "NPLLIq_noticeError",
|
|
243
|
+
"upgradeCommand": "NPLLIq_upgradeCommand",
|
|
244
|
+
"row": "NPLLIq_row",
|
|
245
|
+
"rowMeta": "NPLLIq_rowMeta",
|
|
246
|
+
"checkUpdatesLabel": "NPLLIq_checkUpdatesLabel",
|
|
247
|
+
"dialog": "NPLLIq_dialog",
|
|
248
|
+
"dialogHeader": "NPLLIq_dialogHeader",
|
|
242
249
|
"logModelCol": "NPLLIq_logModelCol",
|
|
250
|
+
"logUsedCol": "NPLLIq_logUsedCol",
|
|
251
|
+
"logSearch": "NPLLIq_logSearch",
|
|
252
|
+
"logPagerControls": "NPLLIq_logPagerControls",
|
|
253
|
+
"fillIdle": "NPLLIq_fillIdle",
|
|
254
|
+
"monitorList": "NPLLIq_monitorList",
|
|
255
|
+
"upgradeBannerText": "NPLLIq_upgradeBannerText",
|
|
243
256
|
"dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
|
|
244
|
-
"
|
|
245
|
-
"
|
|
257
|
+
"version": "NPLLIq_version",
|
|
258
|
+
"dialogTitle": "NPLLIq_dialogTitle",
|
|
246
259
|
"dialogSection": "NPLLIq_dialogSection",
|
|
247
|
-
"
|
|
248
|
-
"
|
|
249
|
-
"
|
|
250
|
-
"logClearBtn": "NPLLIq_logClearBtn",
|
|
251
|
-
"headerText": "NPLLIq_headerText",
|
|
252
|
-
"fillWarn": "NPLLIq_fillWarn",
|
|
260
|
+
"checkBtn": "NPLLIq_checkBtn",
|
|
261
|
+
"save": "NPLLIq_save",
|
|
262
|
+
"selectBtn": "NPLLIq_selectBtn",
|
|
253
263
|
"logDayCol": "NPLLIq_logDayCol",
|
|
254
|
-
"logPagerBtn": "NPLLIq_logPagerBtn",
|
|
255
|
-
"collapse": "NPLLIq_collapse",
|
|
256
|
-
"dialogHint": "NPLLIq_dialogHint",
|
|
257
|
-
"monitorList": "NPLLIq_monitorList",
|
|
258
|
-
"logPagerInfo": "NPLLIq_logPagerInfo",
|
|
259
|
-
"title": "NPLLIq_title",
|
|
260
|
-
"subtitle": "NPLLIq_subtitle",
|
|
261
|
-
"logUsedCol": "NPLLIq_logUsedCol",
|
|
262
|
-
"noticeError": "NPLLIq_noticeError",
|
|
263
|
-
"dialogBody": "NPLLIq_dialogBody",
|
|
264
|
-
"body": "NPLLIq_body",
|
|
265
|
-
"dialogBtn": "NPLLIq_dialogBtn",
|
|
266
|
-
"fillOver": "NPLLIq_fillOver",
|
|
267
|
-
"upgradeCommandRow": "NPLLIq_upgradeCommandRow",
|
|
268
|
-
"upgradeHint": "NPLLIq_upgradeHint",
|
|
269
|
-
"tab": "NPLLIq_tab",
|
|
270
|
-
"upgradeBannerText": "NPLLIq_upgradeBannerText",
|
|
271
264
|
"resetSelect": "NPLLIq_resetSelect",
|
|
272
|
-
"
|
|
265
|
+
"copyBtn": "NPLLIq_copyBtn",
|
|
266
|
+
"logClearDialog": "NPLLIq_logClearDialog",
|
|
267
|
+
"radioRow": "NPLLIq_radioRow",
|
|
268
|
+
"versionNew": "NPLLIq_versionNew",
|
|
269
|
+
"tabIcon": "NPLLIq_tabIcon",
|
|
270
|
+
"logTable": "NPLLIq_logTable",
|
|
271
|
+
"logHeader": "NPLLIq_logHeader",
|
|
272
|
+
"headerActions": "NPLLIq_headerActions"
|
|
273
273
|
};
|
|
274
274
|
//#endregion
|
|
275
275
|
//#region lib/types/client/TokenQuotaPanel.js
|
|
@@ -411,8 +411,7 @@ window.__ModuleLoader__.load({
|
|
|
411
411
|
(0, react.useEffect)(() => {
|
|
412
412
|
const onFocusIn = (event) => {
|
|
413
413
|
const target = event.target;
|
|
414
|
-
|
|
415
|
-
setInputActive(isInput);
|
|
414
|
+
setInputActive(target !== null && (target.matches("input, textarea, [contenteditable=\"true\"]") || target.closest("input, textarea, [contenteditable=\"true\"]") !== null));
|
|
416
415
|
};
|
|
417
416
|
window.addEventListener("focusin", onFocusIn);
|
|
418
417
|
return () => {
|
|
@@ -572,13 +571,13 @@ window.__ModuleLoader__.load({
|
|
|
572
571
|
title: t("upgradeAvailableHint"),
|
|
573
572
|
children: [
|
|
574
573
|
"v",
|
|
575
|
-
"0.1.
|
|
574
|
+
"0.1.19",
|
|
576
575
|
" → v",
|
|
577
576
|
upgrade.latestVersion
|
|
578
577
|
]
|
|
579
578
|
}) : (0, react_jsx_runtime.jsxs)("span", {
|
|
580
579
|
className: TokenQuotaPanel_module_css_default.version,
|
|
581
|
-
children: ["v", "0.1.
|
|
580
|
+
children: ["v", "0.1.19"]
|
|
582
581
|
})]
|
|
583
582
|
}), (0, react_jsx_runtime.jsx)("div", {
|
|
584
583
|
className: TokenQuotaPanel_module_css_default.subtitle,
|
|
@@ -1279,10 +1278,10 @@ window.__ModuleLoader__.load({
|
|
|
1279
1278
|
const TOKEN_QUOTA_NAMESPACE = "token-quota";
|
|
1280
1279
|
/** Snapshot pull cadence in milliseconds. */
|
|
1281
1280
|
const POLL_INTERVAL_MS = 3e3;
|
|
1282
|
-
/** Required services: slot registry,
|
|
1281
|
+
/** Required services: slot registry, sessions, locale, settings scope. */
|
|
1283
1282
|
const inject = [
|
|
1284
1283
|
"slots",
|
|
1285
|
-
"
|
|
1284
|
+
"sessions",
|
|
1286
1285
|
"locale",
|
|
1287
1286
|
"settingsScope"
|
|
1288
1287
|
];
|
|
@@ -1299,7 +1298,7 @@ window.__ModuleLoader__.load({
|
|
|
1299
1298
|
}), "token-quota-ui: panel dictionaries");
|
|
1300
1299
|
const t = ctx.locale.bind(NS);
|
|
1301
1300
|
const scope = ctx.settingsScope.bind({ namespace: TOKEN_QUOTA_NAMESPACE });
|
|
1302
|
-
const
|
|
1301
|
+
const sessions = ctx.get("sessions");
|
|
1303
1302
|
const store = createTokenQuotaPanelStore();
|
|
1304
1303
|
let bound;
|
|
1305
1304
|
let lastGroups = [];
|
|
@@ -1366,11 +1365,11 @@ window.__ModuleLoader__.load({
|
|
|
1366
1365
|
bound?.setFullNotice(t("fullSwitchFailed"));
|
|
1367
1366
|
return;
|
|
1368
1367
|
}
|
|
1369
|
-
|
|
1368
|
+
ctx.remote.session.selectModel({
|
|
1370
1369
|
sessionId: lastSessionId,
|
|
1371
1370
|
provider: target.provider,
|
|
1372
1371
|
model: target.model
|
|
1373
|
-
}).then((
|
|
1372
|
+
}).then((result) => {
|
|
1374
1373
|
if (result.ok) {
|
|
1375
1374
|
lastCurrent = result.value.selected;
|
|
1376
1375
|
flashFullNotice(t("fullSwitchTo").replace("{model}", target.model));
|
|
@@ -1381,14 +1380,21 @@ window.__ModuleLoader__.load({
|
|
|
1381
1380
|
});
|
|
1382
1381
|
};
|
|
1383
1382
|
let pullCount = 0;
|
|
1383
|
+
/** Resolve the session's current model from its durable modelSelection projection. */
|
|
1384
|
+
const currentOf = (sessionId) => {
|
|
1385
|
+
const binding = sessions.binding(sessionId);
|
|
1386
|
+
if (binding === void 0) return null;
|
|
1387
|
+
return binding.session.projections.faceOf("modelSelection").getSnapshot()?.lastUsed ?? null;
|
|
1388
|
+
};
|
|
1384
1389
|
const refreshCurrent = () => {
|
|
1385
1390
|
if (lastSessionId === void 0) return;
|
|
1386
|
-
|
|
1391
|
+
ctx.remote.session.modelCatalog().then((result) => {
|
|
1387
1392
|
if (result.ok) {
|
|
1388
|
-
const
|
|
1393
|
+
const current = currentOf(lastSessionId);
|
|
1394
|
+
const currentChanged = lastCurrent?.provider !== current?.provider || lastCurrent?.model !== current?.model;
|
|
1389
1395
|
lastGroups = result.value.groups;
|
|
1390
|
-
lastCurrent =
|
|
1391
|
-
bound?.setDirectory(result.value.groups,
|
|
1396
|
+
lastCurrent = current;
|
|
1397
|
+
bound?.setDirectory(result.value.groups, current);
|
|
1392
1398
|
if (currentChanged) fetch("/token-quota", { headers: { accept: "application/json" } }).then((response) => response.ok ? response.json() : void 0, () => void 0).then((snapshot) => {
|
|
1393
1399
|
if (snapshot !== void 0) {
|
|
1394
1400
|
bound?.setSnapshot(snapshot);
|
|
@@ -1437,11 +1443,12 @@ window.__ModuleLoader__.load({
|
|
|
1437
1443
|
lastSessionId = sessionId;
|
|
1438
1444
|
bound?.setLoading(true);
|
|
1439
1445
|
bound?.setError(null);
|
|
1440
|
-
|
|
1446
|
+
ctx.remote.session.modelCatalog().then((result) => {
|
|
1441
1447
|
if (result.ok) {
|
|
1448
|
+
const current = currentOf(sessionId);
|
|
1442
1449
|
lastGroups = result.value.groups;
|
|
1443
|
-
lastCurrent =
|
|
1444
|
-
bound?.setDirectory(result.value.groups,
|
|
1450
|
+
lastCurrent = current;
|
|
1451
|
+
bound?.setDirectory(result.value.groups, current);
|
|
1445
1452
|
bound?.setLoading(false);
|
|
1446
1453
|
} else {
|
|
1447
1454
|
bound?.setError(`${result.error.code}: ${result.error.message}`);
|
|
@@ -1512,11 +1519,11 @@ window.__ModuleLoader__.load({
|
|
|
1512
1519
|
scope.set("reset", reset);
|
|
1513
1520
|
};
|
|
1514
1521
|
const selectModel = (sessionId, provider, model) => {
|
|
1515
|
-
|
|
1522
|
+
ctx.remote.session.selectModel({
|
|
1516
1523
|
sessionId,
|
|
1517
1524
|
provider,
|
|
1518
1525
|
model
|
|
1519
|
-
}).then((
|
|
1526
|
+
}).then((result) => {
|
|
1520
1527
|
if (result.ok) {
|
|
1521
1528
|
lastCurrent = result.value.selected;
|
|
1522
1529
|
bound?.setFullNotice(null);
|
package/lib/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { assertTokenQuotaLimit, splitTokenQuotaKey } from "./invariant.js";
|
|
2
|
-
import {
|
|
2
|
+
import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { Service } from "@deepseek-ai/cordis";
|
|
6
6
|
import z from "@deepseek-ai/schemastery";
|
|
7
7
|
import "@deepseek-ai/dsh-agent";
|
|
8
8
|
import { LlmError } from "@deepseek-ai/dsh-llm";
|
|
9
|
-
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
10
9
|
//#region lib/types/types.js
|
|
11
10
|
/**
|
|
12
11
|
* Shared wire/type vocabulary for the daily token-quota plugin.
|
|
@@ -66,7 +65,7 @@ const PACKAGE_NAME = "@jxgame2020/dsh-token-quota";
|
|
|
66
65
|
/** npm registry endpoint for the plugin's latest version. */
|
|
67
66
|
const REGISTRY_LATEST_URL = "https://registry.npmjs.org/@jxgame2020%2Fdsh-token-quota/latest";
|
|
68
67
|
/** How often the Host re-checks for a newer version when update checks are enabled. */
|
|
69
|
-
const UPDATE_CHECK_INTERVAL_MS =
|
|
68
|
+
const UPDATE_CHECK_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
70
69
|
/** Map a detected package manager to its install verb. */
|
|
71
70
|
function installVerb(manager) {
|
|
72
71
|
switch (manager) {
|
|
@@ -91,13 +90,54 @@ function registryUpgradeCommands(profileDir, manager) {
|
|
|
91
90
|
function linkUpgradeCommands(linkPath) {
|
|
92
91
|
return [`git -C "${linkPath}" pull`];
|
|
93
92
|
}
|
|
94
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Detect the profile's package manager; defaults to npm.
|
|
95
|
+
*
|
|
96
|
+
* Lockfiles alone are unreliable: a profile can accumulate several of them over
|
|
97
|
+
* time (e.g. a leftover `pnpm-lock.yaml` next to a fresh `package-lock.json`
|
|
98
|
+
* after switching from pnpm to npm), and a bare existence check then reports
|
|
99
|
+
* the WRONG manager. Instead, look at the installer fingerprints inside
|
|
100
|
+
* `node_modules` — each manager leaves a marker when it actually installs —
|
|
101
|
+
* and pick the most recently touched one. Only if no marker exists fall back
|
|
102
|
+
* to comparing lockfile mtimes.
|
|
103
|
+
*/
|
|
95
104
|
function detectManager(dir) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
const stat = (path) => {
|
|
106
|
+
try {
|
|
107
|
+
return statSync(path).mtimeMs;
|
|
108
|
+
} catch {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const fingerprints = [
|
|
113
|
+
["node_modules/.pnpm", "pnpm"],
|
|
114
|
+
["node_modules/.bun", "bun"],
|
|
115
|
+
["node_modules/.package-lock.json", "npm"],
|
|
116
|
+
["node_modules/.yarn-integrity", "yarn"]
|
|
117
|
+
];
|
|
118
|
+
let best;
|
|
119
|
+
for (const [rel, manager] of fingerprints) {
|
|
120
|
+
const mtime = stat(join(dir, rel));
|
|
121
|
+
if (mtime !== void 0 && (best === void 0 || mtime > best.mtime)) best = {
|
|
122
|
+
manager,
|
|
123
|
+
mtime
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (best !== void 0) return best.manager;
|
|
127
|
+
for (const [rel, manager] of [
|
|
128
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
129
|
+
["yarn.lock", "yarn"],
|
|
130
|
+
["package-lock.json", "npm"],
|
|
131
|
+
["bun.lockb", "bun"],
|
|
132
|
+
["bun.lock", "bun"]
|
|
133
|
+
]) {
|
|
134
|
+
const mtime = stat(join(dir, rel));
|
|
135
|
+
if (mtime !== void 0 && (best === void 0 || mtime > best.mtime)) best = {
|
|
136
|
+
manager,
|
|
137
|
+
mtime
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return best?.manager ?? "npm";
|
|
101
141
|
}
|
|
102
142
|
/**
|
|
103
143
|
* Locate the profile directory that declares this plugin as a dependency, along
|
|
@@ -105,8 +145,7 @@ function detectManager(dir) {
|
|
|
105
145
|
* `undefined` when no profile declares the plugin (e.g. a bundle-managed mount).
|
|
106
146
|
*/
|
|
107
147
|
function findProfile() {
|
|
108
|
-
const
|
|
109
|
-
const profilesDir = join(home, "profiles");
|
|
148
|
+
const profilesDir = join(process.env.DSH_HOME !== void 0 && process.env.DSH_HOME.trim().length > 0 ? process.env.DSH_HOME : join(homedir(), ".dsh"), "profiles");
|
|
110
149
|
let names;
|
|
111
150
|
try {
|
|
112
151
|
names = readdirSync(profilesDir);
|
|
@@ -116,7 +155,11 @@ function findProfile() {
|
|
|
116
155
|
for (const name of names) {
|
|
117
156
|
const dir = join(profilesDir, name);
|
|
118
157
|
const manifestPath = join(dir, "package.json");
|
|
119
|
-
|
|
158
|
+
try {
|
|
159
|
+
if (!statSync(manifestPath).isFile()) continue;
|
|
160
|
+
} catch {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
120
163
|
let manifest;
|
|
121
164
|
try {
|
|
122
165
|
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
@@ -190,8 +233,7 @@ function cycleKey(now, reset) {
|
|
|
190
233
|
}
|
|
191
234
|
/** Default counter file under the Harness home (overridable through config). */
|
|
192
235
|
function defaultStoragePath() {
|
|
193
|
-
|
|
194
|
-
return join(home, "token-quota.json");
|
|
236
|
+
return join(process.env.DSH_HOME ?? join(homedir(), ".dsh"), "token-quota.json");
|
|
195
237
|
}
|
|
196
238
|
/** Combined input + output + cache token count of one provider usage report. */
|
|
197
239
|
function usageTokens(usage) {
|
|
@@ -259,49 +301,51 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
259
301
|
super(ctx, "tokenQuota");
|
|
260
302
|
this.storagePath = config.storagePath !== void 0 && config.storagePath.length > 0 ? config.storagePath : defaultStoragePath();
|
|
261
303
|
this.load();
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
},
|
|
276
|
-
validate: (value) => {
|
|
277
|
-
for (const [key, limit] of Object.entries(value.limits ?? {})) {
|
|
278
|
-
assertTokenQuotaLimit(limit, key);
|
|
279
|
-
splitTokenQuotaKey(key);
|
|
280
|
-
}
|
|
281
|
-
},
|
|
282
|
-
onChange: () => {
|
|
283
|
-
const doc = this.settingsSource();
|
|
284
|
-
this.limits = { ...doc.limits };
|
|
285
|
-
this.monitored = doc.monitored !== void 0 && doc.monitored.length > 0 ? new Set(doc.monitored) : void 0;
|
|
286
|
-
this.checkUpdates = doc.checkUpdates;
|
|
287
|
-
this.syncUpdateChecking();
|
|
288
|
-
if (doc.reset !== void 0 && typeof doc.reset === "object" && doc.reset !== null && typeof doc.reset.offsetHours === "number" && typeof doc.reset.hour === "number" && typeof doc.reset.minute === "number") {
|
|
289
|
-
const configured = doc.reset;
|
|
290
|
-
this.reset = {
|
|
291
|
-
offsetHours: Math.max(-12, Math.min(14, Math.round(configured.offsetHours))),
|
|
292
|
-
hour: Math.max(0, Math.min(23, Math.round(configured.hour))),
|
|
293
|
-
minute: Math.max(0, Math.min(59, Math.round(configured.minute)))
|
|
304
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
305
|
+
settingsCtx.settings.installSection(ctx, TOKEN_QUOTA_NAMESPACE, TOKEN_QUOTA_SETTINGS_SCHEMA, { limits: {} }, {
|
|
306
|
+
setSource: (current) => {
|
|
307
|
+
this.settingsSource = () => {
|
|
308
|
+
const doc = current();
|
|
309
|
+
return {
|
|
310
|
+
limits: doc.limits ?? {},
|
|
311
|
+
monitored: doc.monitored ?? [],
|
|
312
|
+
onFull: doc.onFull === "switchPriority" ? "switchAll" : doc.onFull ?? "stop",
|
|
313
|
+
checkUpdates: doc.checkUpdates ?? true,
|
|
314
|
+
dimWhenIdle: doc.dimWhenIdle ?? false,
|
|
315
|
+
reset: doc.reset ?? void 0
|
|
316
|
+
};
|
|
294
317
|
};
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
318
|
+
},
|
|
319
|
+
validate: (value) => {
|
|
320
|
+
for (const [key, limit] of Object.entries(value.limits ?? {})) {
|
|
321
|
+
assertTokenQuotaLimit(limit, key);
|
|
322
|
+
splitTokenQuotaKey(key);
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
onChange: () => {
|
|
326
|
+
const doc = this.settingsSource();
|
|
327
|
+
this.limits = { ...doc.limits };
|
|
328
|
+
this.monitored = doc.monitored !== void 0 && doc.monitored.length > 0 ? new Set(doc.monitored) : void 0;
|
|
329
|
+
this.checkUpdates = doc.checkUpdates;
|
|
330
|
+
this.syncUpdateChecking();
|
|
331
|
+
if (doc.reset !== void 0 && typeof doc.reset === "object" && doc.reset !== null && typeof doc.reset.offsetHours === "number" && typeof doc.reset.hour === "number" && typeof doc.reset.minute === "number") {
|
|
332
|
+
const configured = doc.reset;
|
|
333
|
+
this.reset = {
|
|
334
|
+
offsetHours: Math.max(-12, Math.min(14, Math.round(configured.offsetHours))),
|
|
335
|
+
hour: Math.max(0, Math.min(23, Math.round(configured.hour))),
|
|
336
|
+
minute: Math.max(0, Math.min(59, Math.round(configured.minute)))
|
|
337
|
+
};
|
|
338
|
+
this.rollCycleIfNeeded();
|
|
339
|
+
} else {
|
|
340
|
+
this.reset = {
|
|
341
|
+
offsetHours: localOffsetHours(),
|
|
342
|
+
hour: 0,
|
|
343
|
+
minute: 0
|
|
344
|
+
};
|
|
345
|
+
this.rollCycleIfNeeded();
|
|
346
|
+
}
|
|
303
347
|
}
|
|
304
|
-
}
|
|
348
|
+
});
|
|
305
349
|
});
|
|
306
350
|
this.tryMountRoute();
|
|
307
351
|
if (this.disposeRoute === void 0) this.disposeRouteWatcher = ctx.on("internal/service", (name) => {
|
|
@@ -409,7 +453,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
409
453
|
return this.upgradeCheck;
|
|
410
454
|
}
|
|
411
455
|
async doRefreshUpgrade() {
|
|
412
|
-
const current = "0.1.
|
|
456
|
+
const current = "0.1.19";
|
|
413
457
|
let latest;
|
|
414
458
|
try {
|
|
415
459
|
const response = await fetch(REGISTRY_LATEST_URL, {
|
|
@@ -505,7 +549,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
|
|
|
505
549
|
/** Read the current snapshot (also used by tests and inspection). */
|
|
506
550
|
readSnapshot() {
|
|
507
551
|
this.rollCycleIfNeeded();
|
|
508
|
-
const keys =
|
|
552
|
+
const keys = new Set([...Object.keys(this.usage), ...Object.keys(this.limits)]);
|
|
509
553
|
const entries = [];
|
|
510
554
|
for (const key of keys) {
|
|
511
555
|
const parts = this.safeSplit(key);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PropsLocale, PropsRuntime, PropsStore } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
-
import type { SessionId } from '@deepseek-ai/dsh-
|
|
2
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
3
3
|
import type { TokenQuotaFullAction, TokenQuotaReset } from '@jxgame2020/dsh-token-quota/types';
|
|
4
4
|
import type { createTokenQuotaPanelStore } from './store.ts';
|
|
5
5
|
/** Injected business face: data loading and mutations wired in `apply`. */
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* full-quota strategy runs: `stop` shows a notice, the other three
|
|
13
13
|
* auto-switch to a suitable model.
|
|
14
14
|
*/
|
|
15
|
-
import type { ClientContext } from '@deepseek-ai/
|
|
15
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
16
16
|
import { type TokenQuotaKey } from './locales.ts';
|
|
17
17
|
export type { TokenQuotaPanelInjected } from './TokenQuotaPanel.tsx';
|
|
18
18
|
export type { ModelQuotaRow, TokenQuotaPanelState } from './store.ts';
|
|
@@ -24,7 +24,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
24
24
|
'token-quota': TokenQuotaKey;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
/** Required services: slot registry,
|
|
27
|
+
/** Required services: slot registry, sessions, locale, settings scope. */
|
|
28
28
|
export declare const inject: string[];
|
|
29
29
|
/**
|
|
30
30
|
* Client plugin body: poll the Host snapshot route, run the full-quota
|
|
@@ -8,8 +8,8 @@ const NS = 'token-quota';
|
|
|
8
8
|
const TOKEN_QUOTA_NAMESPACE = 'token-quota';
|
|
9
9
|
/** Snapshot pull cadence in milliseconds. */
|
|
10
10
|
const POLL_INTERVAL_MS = 3000;
|
|
11
|
-
/** Required services: slot registry,
|
|
12
|
-
export const inject = ['slots', '
|
|
11
|
+
/** Required services: slot registry, sessions, locale, settings scope. */
|
|
12
|
+
export const inject = ['slots', 'sessions', 'locale', 'settingsScope'];
|
|
13
13
|
/**
|
|
14
14
|
* Client plugin body: poll the Host snapshot route, run the full-quota
|
|
15
15
|
* strategy, wire the injected face (directory load, limit write, model
|
|
@@ -20,7 +20,10 @@ export function apply(ctx) {
|
|
|
20
20
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'token-quota-ui: panel dictionaries');
|
|
21
21
|
const t = ctx.locale.bind(NS);
|
|
22
22
|
const scope = ctx.settingsScope.bind({ namespace: TOKEN_QUOTA_NAMESPACE });
|
|
23
|
-
|
|
23
|
+
// The Host `dsh-session` package's `ctx.sessions: SessionStore` merge can
|
|
24
|
+
// shadow the client ISessions face in a combined type-check; the runtime
|
|
25
|
+
// 'sessions' service is the ISessions implementation regardless.
|
|
26
|
+
const sessions = ctx.get('sessions');
|
|
24
27
|
// Store handle shared with the registration; the framework instantiates the
|
|
25
28
|
// live store per entry and hands its bound actions to the inject factory,
|
|
26
29
|
// so the apply-world only ever writes through `bound`.
|
|
@@ -107,11 +110,11 @@ export function apply(ctx) {
|
|
|
107
110
|
bound?.setFullNotice(t('fullSwitchFailed'));
|
|
108
111
|
return;
|
|
109
112
|
}
|
|
110
|
-
void
|
|
113
|
+
void ctx.remote.session.selectModel({
|
|
111
114
|
sessionId: lastSessionId,
|
|
112
115
|
provider: target.provider,
|
|
113
116
|
model: target.model,
|
|
114
|
-
}).then((
|
|
117
|
+
}).then((result) => {
|
|
115
118
|
if (result.ok) {
|
|
116
119
|
lastCurrent = result.value.selected;
|
|
117
120
|
flashFullNotice(t('fullSwitchTo').replace('{model}', target.model));
|
|
@@ -130,16 +133,26 @@ export function apply(ctx) {
|
|
|
130
133
|
// switch on full quota) moves the 「当前」 badge without waiting the full
|
|
131
134
|
// directory-refresh cadence.
|
|
132
135
|
let pullCount = 0;
|
|
136
|
+
/** Resolve the session's current model from its durable modelSelection projection. */
|
|
137
|
+
const currentOf = (sessionId) => {
|
|
138
|
+
const binding = sessions.binding(sessionId);
|
|
139
|
+
if (binding === undefined)
|
|
140
|
+
return null;
|
|
141
|
+
const face = binding.session.projections.faceOf('modelSelection');
|
|
142
|
+
const state = face.getSnapshot();
|
|
143
|
+
return state?.lastUsed ?? null;
|
|
144
|
+
};
|
|
133
145
|
const refreshCurrent = () => {
|
|
134
146
|
if (lastSessionId === undefined)
|
|
135
147
|
return;
|
|
136
|
-
void
|
|
148
|
+
void ctx.remote.session.modelCatalog().then((result) => {
|
|
137
149
|
if (result.ok) {
|
|
138
|
-
const
|
|
139
|
-
|
|
150
|
+
const current = currentOf(lastSessionId);
|
|
151
|
+
const currentChanged = lastCurrent?.provider !== current?.provider
|
|
152
|
+
|| lastCurrent?.model !== current?.model;
|
|
140
153
|
lastGroups = result.value.groups;
|
|
141
|
-
lastCurrent =
|
|
142
|
-
bound?.setDirectory(result.value.groups,
|
|
154
|
+
lastCurrent = current;
|
|
155
|
+
bound?.setDirectory(result.value.groups, current);
|
|
143
156
|
// If the server silently switched to a different model (e.g. the
|
|
144
157
|
// auto-switch on quota exhaustion), re-run the full-quota strategy
|
|
145
158
|
// immediately against the new current model so any further fallback
|
|
@@ -201,11 +214,12 @@ export function apply(ctx) {
|
|
|
201
214
|
lastSessionId = sessionId;
|
|
202
215
|
bound?.setLoading(true);
|
|
203
216
|
bound?.setError(null);
|
|
204
|
-
void
|
|
217
|
+
void ctx.remote.session.modelCatalog().then((result) => {
|
|
205
218
|
if (result.ok) {
|
|
219
|
+
const current = currentOf(sessionId);
|
|
206
220
|
lastGroups = result.value.groups;
|
|
207
|
-
lastCurrent =
|
|
208
|
-
bound?.setDirectory(result.value.groups,
|
|
221
|
+
lastCurrent = current;
|
|
222
|
+
bound?.setDirectory(result.value.groups, current);
|
|
209
223
|
bound?.setLoading(false);
|
|
210
224
|
}
|
|
211
225
|
else {
|
|
@@ -282,7 +296,7 @@ export function apply(ctx) {
|
|
|
282
296
|
void scope.set('reset', reset);
|
|
283
297
|
};
|
|
284
298
|
const selectModel = (sessionId, provider, model) => {
|
|
285
|
-
void
|
|
299
|
+
void ctx.remote.session.selectModel({ sessionId, provider, model }).then((result) => {
|
|
286
300
|
if (result.ok) {
|
|
287
301
|
lastCurrent = result.value.selected;
|
|
288
302
|
// A manual pick clears any leftover auto-switch / exhausted notice.
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module @deepseek-ai/dsh-client-ui-token-quota/client/store
|
|
9
9
|
*/
|
|
10
|
-
import { type EngineStoreHandle } from '@deepseek-ai/dsh-client-
|
|
11
|
-
import type { ModelProviderGroup, ModelSelection } from '@deepseek-ai/dsh-api-
|
|
10
|
+
import { type EngineStoreHandle } from '@deepseek-ai/dsh-client-store';
|
|
11
|
+
import type { ModelProviderGroup, ModelSelection } from '@deepseek-ai/dsh-api-session-controller/types';
|
|
12
12
|
import type { TokenQuotaFullAction, TokenQuotaLog, TokenQuotaReset, TokenQuotaSnapshot, TokenQuotaUpgrade } from '@jxgame2020/dsh-token-quota/types';
|
|
13
13
|
/** One rendered model row: directory identity merged with the quota snapshot. */
|
|
14
14
|
export interface ModelQuotaRow {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module @deepseek-ai/dsh-client-ui-token-quota/client/store
|
|
9
9
|
*/
|
|
10
|
-
import { defineStore } from '@deepseek-ai/dsh-client-
|
|
10
|
+
import { defineStore } from '@deepseek-ai/dsh-client-store';
|
|
11
11
|
/** Build the stable per-model key shared by the counter, settings, and snapshot. */
|
|
12
12
|
function tokenQuotaKey(provider, model) {
|
|
13
13
|
return `${provider}/${model}`;
|
package/lib/types/index.js
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
*
|
|
30
30
|
* @module @jxgame2020/dsh-token-quota
|
|
31
31
|
*/
|
|
32
|
-
import {
|
|
32
|
+
import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
33
33
|
import { homedir } from 'node:os';
|
|
34
34
|
import { dirname, join } from 'node:path';
|
|
35
35
|
import { Service } from '@deepseek-ai/cordis';
|
|
@@ -38,7 +38,6 @@ import z from '@deepseek-ai/schemastery';
|
|
|
38
38
|
// the service listens to without importing any runtime value.
|
|
39
39
|
import '@deepseek-ai/dsh-agent';
|
|
40
40
|
import { LlmError } from '@deepseek-ai/dsh-llm';
|
|
41
|
-
import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
42
41
|
import { TOKEN_QUOTA_EXCEEDED_CODE, TOKEN_QUOTA_NAMESPACE, tokenQuotaKey, } from "./types.js";
|
|
43
42
|
import { assertTokenQuotaLimit, splitTokenQuotaKey } from "./invariant.js";
|
|
44
43
|
/** The plugin's package name, used to locate the profile dependency and the registry endpoint. */
|
|
@@ -76,17 +75,57 @@ function registryUpgradeCommands(profileDir, manager) {
|
|
|
76
75
|
function linkUpgradeCommands(linkPath) {
|
|
77
76
|
return [`git -C "${linkPath}" pull`];
|
|
78
77
|
}
|
|
79
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Detect the profile's package manager; defaults to npm.
|
|
80
|
+
*
|
|
81
|
+
* Lockfiles alone are unreliable: a profile can accumulate several of them over
|
|
82
|
+
* time (e.g. a leftover `pnpm-lock.yaml` next to a fresh `package-lock.json`
|
|
83
|
+
* after switching from pnpm to npm), and a bare existence check then reports
|
|
84
|
+
* the WRONG manager. Instead, look at the installer fingerprints inside
|
|
85
|
+
* `node_modules` — each manager leaves a marker when it actually installs —
|
|
86
|
+
* and pick the most recently touched one. Only if no marker exists fall back
|
|
87
|
+
* to comparing lockfile mtimes.
|
|
88
|
+
*/
|
|
80
89
|
function detectManager(dir) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
const stat = (path) => {
|
|
91
|
+
try {
|
|
92
|
+
return statSync(path).mtimeMs;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
// Installer fingerprints inside node_modules, by manager.
|
|
99
|
+
const fingerprints = [
|
|
100
|
+
['node_modules/.pnpm', 'pnpm'],
|
|
101
|
+
['node_modules/.bun', 'bun'],
|
|
102
|
+
['node_modules/.package-lock.json', 'npm'],
|
|
103
|
+
['node_modules/.yarn-integrity', 'yarn'],
|
|
104
|
+
];
|
|
105
|
+
let best;
|
|
106
|
+
for (const [rel, manager] of fingerprints) {
|
|
107
|
+
const mtime = stat(join(dir, rel));
|
|
108
|
+
if (mtime !== undefined && (best === undefined || mtime > best.mtime)) {
|
|
109
|
+
best = { manager, mtime };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (best !== undefined)
|
|
113
|
+
return best.manager;
|
|
114
|
+
// Fallback: the most recently modified lockfile.
|
|
115
|
+
const lockfiles = [
|
|
116
|
+
['pnpm-lock.yaml', 'pnpm'],
|
|
117
|
+
['yarn.lock', 'yarn'],
|
|
118
|
+
['package-lock.json', 'npm'],
|
|
119
|
+
['bun.lockb', 'bun'],
|
|
120
|
+
['bun.lock', 'bun'],
|
|
121
|
+
];
|
|
122
|
+
for (const [rel, manager] of lockfiles) {
|
|
123
|
+
const mtime = stat(join(dir, rel));
|
|
124
|
+
if (mtime !== undefined && (best === undefined || mtime > best.mtime)) {
|
|
125
|
+
best = { manager, mtime };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return best?.manager ?? 'npm';
|
|
90
129
|
}
|
|
91
130
|
/**
|
|
92
131
|
* Locate the profile directory that declares this plugin as a dependency, along
|
|
@@ -108,8 +147,13 @@ function findProfile() {
|
|
|
108
147
|
for (const name of names) {
|
|
109
148
|
const dir = join(profilesDir, name);
|
|
110
149
|
const manifestPath = join(dir, 'package.json');
|
|
111
|
-
|
|
150
|
+
try {
|
|
151
|
+
if (!statSync(manifestPath).isFile())
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
112
155
|
continue;
|
|
156
|
+
}
|
|
113
157
|
let manifest;
|
|
114
158
|
try {
|
|
115
159
|
manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
@@ -262,60 +306,65 @@ export class TokenQuotaService extends Service {
|
|
|
262
306
|
: defaultStoragePath();
|
|
263
307
|
this.load();
|
|
264
308
|
// Limits live in the user settings document; the Web panel writes them and
|
|
265
|
-
// the section watcher pushes every change back here.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
assertTokenQuotaLimit(limit, key);
|
|
288
|
-
splitTokenQuotaKey(key);
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
onChange: () => {
|
|
292
|
-
const doc = this.settingsSource();
|
|
293
|
-
this.limits = { ...doc.limits };
|
|
294
|
-
this.monitored = doc.monitored !== undefined && doc.monitored.length > 0
|
|
295
|
-
? new Set(doc.monitored)
|
|
296
|
-
: undefined;
|
|
297
|
-
this.checkUpdates = doc.checkUpdates;
|
|
298
|
-
this.syncUpdateChecking();
|
|
299
|
-
if (doc.reset !== undefined
|
|
300
|
-
&& typeof doc.reset === 'object'
|
|
301
|
-
&& doc.reset !== null
|
|
302
|
-
&& typeof doc.reset.offsetHours === 'number'
|
|
303
|
-
&& typeof doc.reset.hour === 'number'
|
|
304
|
-
&& typeof doc.reset.minute === 'number') {
|
|
305
|
-
const configured = doc.reset;
|
|
306
|
-
this.reset = {
|
|
307
|
-
offsetHours: Math.max(-12, Math.min(14, Math.round(configured.offsetHours))),
|
|
308
|
-
hour: Math.max(0, Math.min(23, Math.round(configured.hour))),
|
|
309
|
-
minute: Math.max(0, Math.min(59, Math.round(configured.minute))),
|
|
309
|
+
// the section watcher pushes every change back here. DSH 0.1.5 replaced
|
|
310
|
+
// the standalone `installSettingsSection` helper with
|
|
311
|
+
// `SettingsProvider.installSection`, reached through `ctx.inject` so the
|
|
312
|
+
// settings service is guaranteed to be up before registration.
|
|
313
|
+
ctx.inject(['settings'], (settingsCtx) => {
|
|
314
|
+
settingsCtx.settings.installSection(ctx, TOKEN_QUOTA_NAMESPACE, TOKEN_QUOTA_SETTINGS_SCHEMA, { limits: {} }, {
|
|
315
|
+
setSource: (current) => {
|
|
316
|
+
// Normalize the resolved (schema-shaped) document into the plugin's
|
|
317
|
+
// required shape: empty monitored = monitor everything, absent onFull
|
|
318
|
+
// falls back to 'stop'.
|
|
319
|
+
this.settingsSource = () => {
|
|
320
|
+
const doc = current();
|
|
321
|
+
return {
|
|
322
|
+
limits: doc.limits ?? {},
|
|
323
|
+
monitored: doc.monitored ?? [],
|
|
324
|
+
// Legacy `switchPriority` (removed in 0.1.7) maps to `switchAll`:
|
|
325
|
+
// capped models first, then uncapped — both monitored-only.
|
|
326
|
+
onFull: doc.onFull === 'switchPriority' ? 'switchAll' : (doc.onFull ?? 'stop'),
|
|
327
|
+
checkUpdates: doc.checkUpdates ?? true,
|
|
328
|
+
dimWhenIdle: doc.dimWhenIdle ?? false,
|
|
329
|
+
reset: doc.reset ?? undefined,
|
|
330
|
+
};
|
|
310
331
|
};
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
332
|
+
},
|
|
333
|
+
validate: (value) => {
|
|
334
|
+
for (const [key, limit] of Object.entries(value.limits ?? {})) {
|
|
335
|
+
assertTokenQuotaLimit(limit, key);
|
|
336
|
+
splitTokenQuotaKey(key);
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
onChange: () => {
|
|
340
|
+
const doc = this.settingsSource();
|
|
341
|
+
this.limits = { ...doc.limits };
|
|
342
|
+
this.monitored = doc.monitored !== undefined && doc.monitored.length > 0
|
|
343
|
+
? new Set(doc.monitored)
|
|
344
|
+
: undefined;
|
|
345
|
+
this.checkUpdates = doc.checkUpdates;
|
|
346
|
+
this.syncUpdateChecking();
|
|
347
|
+
if (doc.reset !== undefined
|
|
348
|
+
&& typeof doc.reset === 'object'
|
|
349
|
+
&& doc.reset !== null
|
|
350
|
+
&& typeof doc.reset.offsetHours === 'number'
|
|
351
|
+
&& typeof doc.reset.hour === 'number'
|
|
352
|
+
&& typeof doc.reset.minute === 'number') {
|
|
353
|
+
const configured = doc.reset;
|
|
354
|
+
this.reset = {
|
|
355
|
+
offsetHours: Math.max(-12, Math.min(14, Math.round(configured.offsetHours))),
|
|
356
|
+
hour: Math.max(0, Math.min(23, Math.round(configured.hour))),
|
|
357
|
+
minute: Math.max(0, Math.min(59, Math.round(configured.minute))),
|
|
358
|
+
};
|
|
359
|
+
this.rollCycleIfNeeded();
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
// null / malformed (unset) = machine-local midnight — the default.
|
|
363
|
+
this.reset = { offsetHours: localOffsetHours(), hour: 0, minute: 0 };
|
|
364
|
+
this.rollCycleIfNeeded();
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
});
|
|
319
368
|
});
|
|
320
369
|
// Snapshot route: optional — only mounted when a webServer service exists
|
|
321
370
|
// (the Web profile); headless deployments keep the enforcement without it.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxgame2020/dsh-token-quota",
|
|
3
3
|
"description": "Daily per-model token quota accounting, enforcement, and a floating quota panel for the DeepSeek Harness — a fully self-contained plugin (no core-Harness wiring, no generated RPC contract)",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.19",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -31,8 +31,9 @@
|
|
|
31
31
|
"dsh": {
|
|
32
32
|
"client": {
|
|
33
33
|
"inject": [
|
|
34
|
+
"@deepseek-ai/dsh-api-session-controller",
|
|
34
35
|
"@deepseek-ai/dsh-client-locale",
|
|
35
|
-
"@deepseek-ai/dsh-client-
|
|
36
|
+
"@deepseek-ai/dsh-client-store",
|
|
36
37
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
37
38
|
"@deepseek-ai/dsh-client-ui-slots",
|
|
38
39
|
"@deepseek-ai/dsh-api-remotes"
|
|
@@ -56,28 +57,31 @@
|
|
|
56
57
|
"peerDependencies": {
|
|
57
58
|
"@deepseek-ai/cordis": "*",
|
|
58
59
|
"@deepseek-ai/dsh-agent": "*",
|
|
60
|
+
"@deepseek-ai/dsh-api-gateway": "*",
|
|
59
61
|
"@deepseek-ai/dsh-api-remotes": "*",
|
|
62
|
+
"@deepseek-ai/dsh-api-session-controller": "*",
|
|
63
|
+
"@deepseek-ai/dsh-attachment": "*",
|
|
64
|
+
"@deepseek-ai/dsh-brand": "*",
|
|
60
65
|
"@deepseek-ai/dsh-client-connection": "*",
|
|
61
66
|
"@deepseek-ai/dsh-client-locale": "*",
|
|
62
|
-
"@deepseek-ai/dsh-client-
|
|
67
|
+
"@deepseek-ai/dsh-client-store": "*",
|
|
63
68
|
"@deepseek-ai/dsh-client-ui-layout": "*",
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-renderer": "*",
|
|
70
|
+
"@deepseek-ai/dsh-client-ui-session": "*",
|
|
64
71
|
"@deepseek-ai/dsh-client-ui-settings": "*",
|
|
65
72
|
"@deepseek-ai/dsh-client-ui-slots": "*",
|
|
66
73
|
"@deepseek-ai/dsh-invariants": "*",
|
|
74
|
+
"@deepseek-ai/dsh-jobs": "*",
|
|
67
75
|
"@deepseek-ai/dsh-llm": "*",
|
|
68
76
|
"@deepseek-ai/dsh-session": "*",
|
|
69
|
-
"@deepseek-ai/dsh-settings": "*",
|
|
70
|
-
"react": "^18.2.0",
|
|
71
|
-
"@deepseek-ai/dsh-host-apiproxy": "*",
|
|
72
|
-
"@deepseek-ai/dsh-attachment": "*",
|
|
73
|
-
"@deepseek-ai/dsh-brand": "*",
|
|
74
|
-
"@deepseek-ai/dsh-jobs": "*",
|
|
75
77
|
"@deepseek-ai/dsh-session-projection": "*",
|
|
78
|
+
"@deepseek-ai/dsh-settings": "*",
|
|
76
79
|
"@deepseek-ai/dsh-tools": "*",
|
|
77
80
|
"@deepseek-ai/dsh-user-approval": "*",
|
|
78
81
|
"@deepseek-ai/dsh-user-questions": "*",
|
|
79
82
|
"@deepseek-ai/dsh-workspace": "*",
|
|
80
|
-
"@deepseek-ai/schemastery": "*"
|
|
83
|
+
"@deepseek-ai/schemastery": "*",
|
|
84
|
+
"react": "^18.2.0"
|
|
81
85
|
},
|
|
82
86
|
"peerDependenciesMeta": {
|
|
83
87
|
"@deepseek-ai/cordis": {
|