feat: DeepSeek balance + session spend status bar plugin for DSH

This commit is contained in:
GeekRicardo
2026-08-14 17:51:15 +08:00
commit 717ae61e96
9 changed files with 762 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
node_modules/
lib/*.map
.DS_Store
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 GeekRicardo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+63
View File
@@ -0,0 +1,63 @@
# deepseek-balance
DeepSeek Harness web 插件:在**输入框下方状态栏**展示 DeepSeek 官方账户余额与本会话估算花费,并区分非 DeepSeek 模型。
- DeepSeek 模型时:`● 本会话 ¥X.XX · ● 余额 ¥465.xx`(每 60 秒自动刷新)
- 非 DeepSeek 模型时:`● 非 DeepSeek 供应商`(不查余额、不算花费)
## 一键安装
```bash
curl -fsSL https://raw.githubusercontent.com/GeekRicardo/deepseek-balance/main/install.sh | bash
```
脚本做的事(可先 `--dry-run` 预览):
1.`~/.dsh/profiles/web/package.json` 写入依赖 `"deepseek-balance": "github:GeekRicardo/deepseek-balance"`
2.`deepseek-balance` 追加进 `dsh.profile.bundles`
3. `cd ~/.dsh/profiles/web && pnpm install`
4. 校验 bundles 已注册,提示重启。
重启 DSH 并硬刷新页面后生效:
```bash
pm2 restart dsh-web # 若用 pm2 托管;否则用你的启动方式重启
```
## 卸载
```bash
# 1. 从 ~/.dsh/profiles/web/package.json 的 dsh.profile.bundles 移除 "deepseek-balance"
# 2. 移除 dependencies 里的 "deepseek-balance"
# 3. cd ~/.dsh/profiles/web && pnpm install
# 4. 重启 DSH
```
## 前置条件
- DeepSeek Harness 已初始化 web profile`~/.dsh/profiles/web` 存在)。
- `~/.dsh/.credentials.yaml` 里配置了 `DEEPSEEK_API_KEY`(本插件复用 harness 自身的 DeepSeek 密钥,不额外索取)。
- Node.js ≥ 20、pnpm 可用。
## 工作原理
| 半区 | 职责 |
| --- | --- |
| Host | 监听 `llm/stream` 按 session 累计 DeepSeek token;从 models.dev 拉单价(内存缓存 24h + 硬编码兜底)换算人民币估算花费;经 `credentials.resolve('DEEPSEEK_API_KEY')` 读密钥,curl 官方 `/user/balance` 查余额;注册 `/deepseek-balance/status` HTTP route |
| Client | 在 `conversation.composer.dock` 槽位渲染状态栏,`fetch` 轮询该 route60s |
### 计费口径(重要)
- DeepSeek 官方 API **不返回金额**,只返回 token 数。金额是 `token × 单价` 的**估算**,不是账单。
- 单价来自第三方 [models.dev](https://models.dev)(USD/百万 token),按模型前缀匹配;拉取失败回落到内置单价。
- 汇率固定 7.2 换算成人民币。
- 误差来源:DeepSeek 峰谷定价(分时段计价)、汇率、单价维护。
### 数据生命周期
- 本会话花费是**内存态**,从插件加载后的下一次模型调用开始累计,重启清零,不持久化。
- 余额每次轮询实时查询官方接口。
## License
MIT
+7
View File
@@ -0,0 +1,7 @@
# deepseek-balance bundle patch
#
# 安装后(dependencies + dsh.profile.bundles 均含 deepseek-balance),
# 启动时 profile boot 会合并本 patch,把插件行插入 host composition。
- insert:
- id: deepseek-balance
name: 'deepseek-balance'
+12
View File
@@ -0,0 +1,12 @@
{
"name": "deepseek-balance",
"description": "DeepSeek Harness web 插件:状态栏展示 DeepSeek 余额与本会话估算花费",
"version": "1.0.0",
"entry": {
"name": "deepseek-balance",
"inject": []
},
"client": {
"platform": "web"
}
}
Executable
+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/env bash
# =============================================================================
# deepseek-balance 一键安装脚本(macOS / Linux / Windows Git Bash
#
# 包只在 GitHub、不发 npm,因此本脚本直接把 github 依赖写进 profile 的
# package.jsondependencies + dsh.profile.bundles),再 pnpm install 拉取。
# 下次启动 DSH 时 profile boot 会读取包内 cordis.patch.yml 自动挂载插件行。
#
# 用法:
# curl -fsSL https://raw.githubusercontent.com/GeekRicardo/deepseek-balance/main/install.sh | bash
#
# 或下载后本地运行:bash install.sh [--dry-run] [--restart]
#
# --dry-run 只打印将要执行的操作,不写任何文件。
# --restart 装完后尝试 `pm2 restart dsh-web`(无 pm2 时仅提示)。
# -h/--help 打印本帮助。
#
# 环境变量(均可省略):DSH_HOME(默认 ~/.dsh
# =============================================================================
set -euo pipefail
for arg in "$@"; do
if [ "$arg" = "-h" ] || [ "$arg" = "--help" ]; then
cat <<'EOF'
deepseek-balance 一键安装脚本
用法:
curl -fsSL https://raw.githubusercontent.com/GeekRicardo/deepseek-balance/main/install.sh | bash
或:bash install.sh [--dry-run] [--restart]
--dry-run 只打印将要执行的操作,不写任何文件
--restart 装完后尝试 `pm2 restart dsh-web`(无 pm2 时仅提示)
环境变量(可省略):DSH_HOME(默认 ~/.dsh
EOF
exit 0
fi
done
DSH_HOME="${DSH_HOME:-${HOME:-${USERPROFILE:-}}/.dsh}"
PROFILE_DIR="$DSH_HOME/profiles/web"
PKG_JSON="$PROFILE_DIR/package.json"
PKG="deepseek-balance"
GH_DEP="github:GeekRicardo/deepseek-balance"
DRY_RUN=false
RESTART=false
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
--restart) RESTART=true ;;
-h|--help) : ;;
*) echo "未知参数: ${arg}(用 -h 查看用法)" >&2; exit 2 ;;
esac
done
say() { printf '\033[32m[install]\033[0m %s\n' "$*"; }
warn() { printf '\033[33m[warn]\033[0m %s\n' "$*" >&2; }
die() { printf '\033[31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
command -v node >/dev/null 2>&1 || die "未找到 nodeDSH 需要 Node.js ≥ 20"
command -v pnpm >/dev/null 2>&1 || die "未找到 pnpm"
[ -d "$PROFILE_DIR" ] || die "找不到 profile 目录:${PROFILE_DIR}(请先安装并运行过一次 dsh web)"
[ -f "$PKG_JSON" ] || die "找不到 ${PKG_JSON}"
if [ "$DRY_RUN" = true ]; then
say "[dry-run] 步骤 1:在 ${PKG_JSON} 写入 dependencies[\"${PKG}\"]=\"${GH_DEP}\""
say "[dry-run] 步骤 2:在 dsh.profile.bundles 追加 \"${PKG}\""
say "[dry-run] 步骤 3cd ${PROFILE_DIR} && pnpm install"
say "[dry-run] 步骤 4:校验 dsh.profile.bundles 含 ${PKG}"
[ "$RESTART" = true ] && say "[dry-run] 步骤 5pm2 restart dsh-web" || say "[dry-run] 步骤 5:提示用户重启 DSH"
exit 0
fi
say "目标 profile${PROFILE_DIR}"
# 步骤 1+2:幂等写 dependencies + bundles
UPDATE_RESULT="$(node -e '
const fs = require("fs");
const p = process.argv[1];
const dep = process.argv[2];
const pkg = process.argv[3];
const json = JSON.parse(fs.readFileSync(p, "utf8"));
let changed = false;
json.dependencies = json.dependencies || {};
if (json.dependencies[pkg] !== dep) {
json.dependencies[pkg] = dep;
changed = true;
}
json.dsh = json.dsh || {};
json.dsh.profile = json.dsh.profile || {};
json.dsh.profile.bundles = Array.isArray(json.dsh.profile.bundles) ? json.dsh.profile.bundles : [];
if (!json.dsh.profile.bundles.includes(pkg)) {
json.dsh.profile.bundles.push(pkg);
changed = true;
}
if (changed) {
fs.writeFileSync(p, JSON.stringify(json, null, 2) + "\n");
console.log("updated");
} else {
console.log("unchanged");
}
' "$PKG_JSON" "$GH_DEP" "$PKG")"
[ "$UPDATE_RESULT" = "updated" ] \
&& say "已写入 dependencies + dsh.profile.bundles${PKG} = ${GH_DEP}" \
|| say "dependencies + bundles 已就绪,跳过"
# 步骤 3:安装依赖
say "执行 pnpm install(拉取 GitHub 包,可能耗时)..."
( cd "$PROFILE_DIR" && pnpm install )
# 步骤 4:校验 bundles 已注册
if ! node -e '
const fs = require("fs");
const p = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
const bundles = p.dsh?.profile?.bundles ?? [];
process.exit(bundles.includes(process.argv[2]) ? 0 : 1);
' "$PKG_JSON" "$PKG"; then
die "deepseek-balance 未出现在 dsh.profile.bundles 中,挂载未注册,请检查 pnpm install 输出。"
fi
say "bundle 已注册:dsh.profile.bundles 包含 ${PKG}(下次启动自动挂载)"
say "安装完成:${PKG}"
# 步骤 5:重启提示
if [ "$RESTART" = true ]; then
if command -v pm2 >/dev/null 2>&1; then
say "重启 dsh-webpm2..."
pm2 restart dsh-web || warn "pm2 restart 失败,请手动重启 DSH"
else
warn "未找到 pm2,请手动重启 DSH"
fi
else
say "下一步:重启 DSH 并硬刷新(Cmd/Ctrl+Shift+R)使插件生效。"
if command -v pm2 >/dev/null 2>&1; then
say "本机可用:pm2 restart dsh-web(会短暂断开当前页面会话)"
fi
fi
+164
View File
@@ -0,0 +1,164 @@
window.__ModuleLoader__.load({
id: "deepseek-balance",
factory: (require) => {
var module = { exports: {} };
var exports = module.exports;
var React = require("react");
var inject = ["slots"];
var STYLE_ID = "deepseek-balance-style";
var cssText =
".dsb-readout { display: inline-flex; align-items: center; gap: 10px; font-size: 11px; line-height: 1.5; " +
"color: var(--dsw-alias-label-secondary); white-space: nowrap; user-select: none; } " +
".dsb-readout__item { display: inline-flex; align-items: center; } " +
".dsb-readout__dot { width: 6px; height: 6px; border-radius: 50%; margin-right: 5px; flex: none; " +
"background: var(--dsw-alias-state-success-primary); } " +
".dsb-readout__dot--off { background: var(--dsw-alias-state-error-primary); } " +
".dsb-readout__dot--muted { background: var(--dsw-alias-label-secondary); } " +
".dsb-readout--error { color: var(--dsw-alias-state-error-primary); }";
function ensureStyle() {
if (document.getElementById(STYLE_ID) !== null) return;
var style = document.createElement("style");
style.id = STYLE_ID;
style.textContent = cssText;
document.head.appendChild(style);
}
function fmtMoney(cny) {
var n = Number(cny);
var val = n === n ? n : 0;
if (val > 0 && val < 0.01) return "¥" + val.toFixed(4);
return "¥" + val.toFixed(2);
}
function currencySymbol(code) {
if (code === "CNY") return "¥";
if (code === "USD") return "$";
if (code === "EUR") return "€";
return code + " ";
}
function StatusReadout(props) {
var sessionId = props && props.sessionId;
var viewState = React.useState({ phase: "loading" });
var view = viewState[0];
var setView = viewState[1];
React.useEffect(
function () {
var alive = true;
function load() {
var qs = sessionId ? "?sessionId=" + encodeURIComponent(String(sessionId)) : "";
fetch("/deepseek-balance/status" + qs)
.then(function (r) {
return r.json();
})
.then(function (res) {
if (!alive) return;
if (res && res.ok) setView({ phase: "ok", res: res });
else setView({ phase: "error", message: (res && res.error) || "查询失败" });
})
.catch(function (err) {
if (!alive) return;
setView({ phase: "error", message: String((err && err.message) || err) });
});
}
load();
var timer = setInterval(load, 60000);
return function () {
alive = false;
clearInterval(timer);
};
},
[sessionId],
);
if (view.phase === "loading") {
return React.createElement("span", { className: "dsb-readout" }, "DeepSeek ···");
}
if (view.phase === "error") {
return React.createElement("span", { className: "dsb-readout dsb-readout--error" }, "DeepSeek 状态不可用");
}
var res = view.res;
if (!res.isDeepSeek) {
return React.createElement(
"span",
{
className: "dsb-readout",
title: "当前模型:" + (res.model || "未知") + ",非 DeepSeek 官方,不展示余额/花费",
},
React.createElement("span", { className: "dsb-readout__dot dsb-readout__dot--muted" }),
"非 DeepSeek 供应商",
);
}
var items = [];
var pricingTitle = res.pricingSource === "live" ? "定价来自 models.dev(实时)" : "定价来自内置兜底";
items.push(
React.createElement(
"span",
{
key: "spend",
className: "dsb-readout__item",
title: "本会话估算花费(" + res.requests + " 次请求," + pricingTitle + "",
},
React.createElement("span", { className: "dsb-readout__dot" }),
"本会话 " + fmtMoney(res.spendCny),
),
);
if (res.balance) {
var infos = Array.isArray(res.balance.balance_infos) ? res.balance.balance_infos : [];
var parts = infos.map(function (info) {
var sym = currencySymbol(info.currency || "CNY");
var n = Number(info.total_balance);
var text = n === n ? sym + n.toFixed(2) : String(info.total_balance);
return (info.currency || "") + " " + text;
});
var available = res.balance.is_available !== false;
items.push(
React.createElement(
"span",
{ key: "balance", className: "dsb-readout__item", title: "DeepSeek 账户余额" },
React.createElement(
"span",
{ className: "dsb-readout__dot" + (available ? "" : " dsb-readout__dot--off") },
),
"余额 " + (parts.length ? parts.join(" · ") : "—"),
),
);
} else {
items.push(
React.createElement(
"span",
{ key: "balance", className: "dsb-readout__item dsb-readout--error", title: res.balanceError || "余额不可用" },
"余额不可用",
),
);
}
return React.createElement("span", { className: "dsb-readout" }, items);
}
function apply(ctx) {
ensureStyle();
ctx.slots.inject("conversation.composer.dock", function () {
return ctx.slots.register(
{ name: "conversation.composer.dock", id: "deepseek-balance", order: 10, label: "DeepSeek 余额" },
function (props) {
return React.createElement(StatusReadout, { sessionId: props && props.sessionId });
},
);
});
}
exports.apply = apply;
exports.inject = inject;
return module.exports;
},
});
+297
View File
@@ -0,0 +1,297 @@
// deepseek-balance — host face
//
// 职责(全部进程内、可逆):
// 1. 监听 `llm/stream` 瀑布事件,按 session 累计 DeepSeek 模型的 token 用量。
// 2. 从 models.dev 拉取 DeepSeek 单价(内存缓存 24h + 硬编码兜底),
// 展示时用最新单价把累计 token 换算成人民币估算花费。
// 3. 通过 credentials 服务读取 DEEPSEEK_API_KEYcurl 官方 /user/balance 查余额。
// 4. 注册 /deepseek-balance/status HTTP route 供 client 轮询。
const inject = ["webServer", "credentials", "subprocess"];
const CNY_PER_USD = 7.2;
const PRICING_TTL_MS = 24 * 60 * 60 * 1000;
const MODELS_DEV_URL = "https://models.dev/api.json";
// models.dev 拉取失败时的兜底单价,USD / 百万 token。
const FALLBACK_PRICING = [
{ prefix: "deepseek-v4-pro", input: 0.435, output: 0.87, cacheRead: 0.003625 },
{ prefix: "deepseek-v4-flash", input: 0.14, output: 0.28, cacheRead: 0.0028 },
{ prefix: "deepseek-chat", input: 0.14, output: 0.28, cacheRead: 0.0028 },
{ prefix: "deepseek-reasoner", input: 0.14, output: 0.28, cacheRead: 0.0028 },
];
function isDeepSeekModel(model) {
return typeof model === "string" && model.indexOf("deepseek-") === 0;
}
function round2(n) {
return Math.round(n * 100) / 100;
}
function writeJson(res, status, body) {
const payload = JSON.stringify(body);
res.writeHead(status, {
"content-type": "application/json; charset=utf-8",
"cache-control": "no-store",
});
res.end(payload);
}
function isLoopbackHostname(hostname) {
if (hostname === "localhost" || hostname === "::1" || hostname === "[::1]") return true;
const parts = hostname.split(".");
if (parts.length !== 4 || parts[0] !== "127") return false;
return parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
}
// 同源/本机请求防护:拒绝跨站读取余额,只放行 loopback 同源请求。
function isTrusted(req) {
const host = req.headers.host;
if (!host) return false;
const hostname = host.split(":")[0];
if (!isLoopbackHostname(hostname)) return false;
if (req.headers["sec-fetch-site"] === "cross-site") return false;
const origin = req.headers.origin;
if (origin === undefined) return true;
try {
return new URL(origin).host === host;
} catch {
return false;
}
}
function apply(ctx) {
const credentials = ctx.credentials;
const subprocess = ctx.subprocess;
const agentDefaultModel = ctx.get("agentDefaultModel");
const pricing = { entries: null, fetchedAt: 0, source: "fallback" };
let pricingPromise = null;
const state = { lastModel: null };
const tokensBySession = new Map();
function recordUsage(options, usage) {
if (!usage || !isDeepSeekModel(options && options.model)) return;
const key = options.sessionId ? String(options.sessionId) : "__global__";
const cur = tokensBySession.get(key) || {
inputTokens: 0,
cacheReadTokens: 0,
outputTokens: 0,
requests: 0,
};
cur.inputTokens += usage.inputTokens || 0;
cur.cacheReadTokens += usage.cacheReadTokens || 0;
cur.outputTokens += usage.outputTokens || 0;
cur.requests += 1;
tokensBySession.set(key, cur);
}
// 透传每个 chunk,流结束后把 usage 记入本会话累计;不改变下游语义。
ctx.on("llm/stream", function (options, next) {
if (options && typeof options.model === "string") state.lastModel = options.model;
const upstream = next();
return (async function* () {
let usage = null;
try {
for await (const chunk of upstream) {
if (chunk && chunk.type === "usage" && chunk.usage) usage = chunk.usage;
yield chunk;
}
} finally {
if (usage) {
try {
recordUsage(options, usage);
} catch (error) {
console.error("deepseek-balance: record usage failed", error);
}
}
}
})();
});
function defaultModel() {
if (!agentDefaultModel) return null;
try {
const sel = agentDefaultModel.currentSelection();
return sel && typeof sel.model === "string" ? sel.model : null;
} catch {
return null;
}
}
function httpGet(url) {
const handle = subprocess.spawn({
argv: ["curl", "-sS", "--max-time", "20", url],
cwd: "/",
stdio: { stdin: "ignore", stdout: { maxBytes: 8388608 }, stderr: { maxBytes: 16384 } },
graceMs: 5000,
});
return handle.done.then(function (outcome) {
const out = handle.collected.stdout ? handle.collected.stdout.readFrom(0).text : "";
const err = handle.collected.stderr ? handle.collected.stderr.readFrom(0).text : "";
if (outcome.exitCode !== 0) {
throw new Error((err && err.trim()) ? err.trim() : "curl exit " + outcome.exitCode);
}
return out;
});
}
function parseDeepSeekPricing(body) {
const data = JSON.parse(body);
const models = data && data.deepseek && data.deepseek.models;
if (!models || typeof models !== "object") return null;
const entries = [];
for (const id in models) {
const cost = models[id] && models[id].cost;
if (!cost || typeof cost !== "object") continue;
const input = typeof cost.input === "number" ? cost.input : 0;
const output = typeof cost.output === "number" ? cost.output : 0;
const cacheRead = typeof cost.cache_read === "number" ? cost.cache_read : 0;
if (!(input > 0) && !(output > 0)) continue;
entries.push({ prefix: id, input, output, cacheRead });
}
if (!entries.length) return null;
entries.sort(function (a, b) {
return b.prefix.length - a.prefix.length;
});
return entries;
}
function ensurePricing() {
const now = Date.now();
if (pricing.entries && now - pricing.fetchedAt < PRICING_TTL_MS) return Promise.resolve();
if (pricingPromise) return pricingPromise;
pricingPromise = (async function () {
try {
const body = await httpGet(MODELS_DEV_URL);
const entries = parseDeepSeekPricing(body);
if (entries) {
pricing.entries = entries;
pricing.fetchedAt = Date.now();
pricing.source = "live";
}
} catch (error) {
console.error("deepseek-balance: fetch pricing failed, using fallback", error);
} finally {
pricingPromise = null;
}
})();
return pricingPromise;
}
function matchPricing(model) {
const entries = pricing.entries || FALLBACK_PRICING;
for (let i = 0; i < entries.length; i++) {
if (model.indexOf(entries[i].prefix) === 0) return entries[i];
}
return null;
}
async function queryBalance() {
let resolved;
try {
resolved = await credentials.resolve("DEEPSEEK_API_KEY");
} catch {
return { ok: false, error: "读取凭据失败" };
}
if (!resolved || !resolved.value) return { ok: false, error: "未配置 DEEPSEEK_API_KEY" };
// 密钥经 curl 的 stdin config 注入,避免出现在 argv(ps 可见)。
const config =
'header = "Authorization: Bearer ' + resolved.value + '"\n' +
'header = "Accept: application/json"\n';
let handle;
try {
handle = subprocess.spawn({
argv: ["curl", "-sS", "--max-time", "15", "--config", "-", "https://api.deepseek.com/user/balance"],
cwd: "/",
stdio: { stdin: { data: config }, stdout: { maxBytes: 65536 }, stderr: { maxBytes: 16384 } },
graceMs: 5000,
});
} catch {
return { ok: false, error: "启动 curl 失败" };
}
let outcome;
try {
outcome = await handle.done;
} catch {
return { ok: false, error: "curl 执行异常" };
}
const out = handle.collected.stdout ? handle.collected.stdout.readFrom(0).text : "";
const err = handle.collected.stderr ? handle.collected.stderr.readFrom(0).text : "";
if (outcome.exitCode !== 0) {
return { ok: false, error: (err && err.trim()) ? err.trim() : "curl exit " + outcome.exitCode };
}
let body;
try {
body = JSON.parse(out);
} catch {
return { ok: false, error: "响应不是合法 JSON" };
}
return { ok: true, body };
}
async function getStatus(sessionId) {
const key = sessionId ? String(sessionId) : "__global__";
const model = state.lastModel || defaultModel();
const tokens = tokensBySession.get(key) || {
inputTokens: 0,
cacheReadTokens: 0,
outputTokens: 0,
requests: 0,
};
if (!isDeepSeekModel(model)) {
return { ok: true, isDeepSeek: false, model: model || null, requests: tokens.requests };
}
await ensurePricing();
const p = matchPricing(model);
const usd =
(p ? p.input : 0) * (tokens.inputTokens / 1000000) +
(p ? p.cacheRead : 0) * (tokens.cacheReadTokens / 1000000) +
(p ? p.output : 0) * (tokens.outputTokens / 1000000);
const spendCny = round2(usd * CNY_PER_USD);
const balance = await queryBalance();
return {
ok: true,
isDeepSeek: true,
model: model,
spendCny: spendCny,
requests: tokens.requests,
pricingSource: pricing.source,
balance: balance.ok ? balance.body : null,
balanceError: balance.ok ? null : balance.error,
};
}
ctx.effect(
() =>
ctx.webServer.register({
kind: "exact",
path: "/deepseek-balance/status",
handler: async (req, res) => {
if (!isTrusted(req)) {
writeJson(res, 403, { ok: false, error: "forbidden" });
return;
}
try {
const url = new URL(req.url ?? "/", "http://dsh.internal");
const sessionId = url.searchParams.get("sessionId");
writeJson(res, 200, await getStatus(sessionId));
} catch (error) {
writeJson(res, 500, { ok: false, error: error && error.message ? error.message : String(error) });
}
},
}),
"deepseek-balance: /deepseek-balance/status route",
);
}
export { apply, inject };
+57
View File
@@ -0,0 +1,57 @@
{
"name": "deepseek-balance",
"version": "1.0.0",
"description": "DeepSeek Harness web 插件:在输入框下方状态栏展示 DeepSeek 官方账户余额与本会话估算花费(token × models.dev 实时单价),并区分非 DeepSeek 模型。",
"type": "module",
"main": "lib/index.js",
"exports": {
".": "./lib/index.js",
"./client": "./lib/client.js",
"./package.json": "./package.json"
},
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
},
"client": {
"platform": "web",
"inject": [
"@deepseek-ai/dsh-client-runtime",
"@deepseek-ai/dsh-client-ui-slots"
]
}
},
"files": [
"lib/index.js",
"lib/client.js",
"cordis.patch.yml",
"dsh.plugin.json",
"README.md",
"LICENSE",
"install.sh"
],
"license": "MIT",
"engines": {
"node": ">=20"
},
"repository": {
"type": "git",
"url": "https://github.com/GeekRicardo/deepseek-balance"
},
"peerDependencies": {
"@deepseek-ai/cordis": "*",
"@deepseek-ai/dsh-host-webserver": "*",
"@deepseek-ai/dsh-credentials": "*",
"@deepseek-ai/dsh-subprocess": "*",
"@deepseek-ai/dsh-client-ui-slots": "*",
"react": "^18.2.0"
},
"peerDependenciesMeta": {
"@deepseek-ai/cordis": { "optional": true },
"@deepseek-ai/dsh-host-webserver": { "optional": true },
"@deepseek-ai/dsh-credentials": { "optional": true },
"@deepseek-ai/dsh-subprocess": { "optional": true },
"@deepseek-ai/dsh-client-ui-slots": { "optional": true },
"react": { "optional": true }
}
}