@kenz1117/dsh-ui-usage-billing 1.4.18 → 1.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/client.js +5 -5
- package/lib/index.js +200 -144
- package/lib/types/aggregate.d.ts +4 -2
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/usage-billing-settings.d.ts +6 -4
- package/lib/types/index.d.ts +22 -3
- package/package.json +3 -3
package/lib/index.js
CHANGED
|
@@ -2422,8 +2422,10 @@ function emptyUsage() {
|
|
|
2422
2422
|
* @param pricedCost - 调用方预算好的本次费用(同一条消息折叠进多个桶时计价结果相同,
|
|
2423
2423
|
* 逐桶重算会让最热路径付出 N 倍常数);提供时跳过内部的计价闸门与计算,
|
|
2424
2424
|
* 调用方保证其已按 `!subscription && isPriced(key)` 判定。
|
|
2425
|
+
* @param unverifiedChannel - 本次调用是否来自未知通道(配置外 provider)。未知通道的
|
|
2426
|
+
* 支出主体无法核实,费用一律记 0(token 照常统计,issue #82)。
|
|
2425
2427
|
*/
|
|
2426
|
-
function foldUsage(acc, usage, key, subscription, timeMs, official = false, pricedCost) {
|
|
2428
|
+
function foldUsage(acc, usage, key, subscription, timeMs, official = false, pricedCost, unverifiedChannel = false) {
|
|
2427
2429
|
const cacheHit = usage.cacheReadTokens ?? 0;
|
|
2428
2430
|
const cacheMiss = usage.inputTokens + (usage.cacheWriteTokens ?? 0);
|
|
2429
2431
|
acc.calls += 1;
|
|
@@ -2439,7 +2441,7 @@ function foldUsage(acc, usage, key, subscription, timeMs, official = false, pric
|
|
|
2439
2441
|
if (official) acc.officialCost += pricedCost;
|
|
2440
2442
|
return;
|
|
2441
2443
|
}
|
|
2442
|
-
if (!subscription && isPriced(key)) {
|
|
2444
|
+
if (!subscription && !unverifiedChannel && isPriced(key)) {
|
|
2443
2445
|
const thisCost = computeCostAt(modelOf(key), {
|
|
2444
2446
|
input: cacheHit + cacheMiss,
|
|
2445
2447
|
cacheHit,
|
|
@@ -2994,8 +2996,9 @@ function foldInto(fold, machine, events, subscriptionProviders, officialProvider
|
|
|
2994
2996
|
}
|
|
2995
2997
|
const modelKey = key;
|
|
2996
2998
|
const day = dayStamp(event.time);
|
|
2997
|
-
const
|
|
2998
|
-
|
|
2999
|
+
const unverifiedChannel = siteBucket === "unknown";
|
|
3000
|
+
const priced = !subscription && !unverifiedChannel && isPriced(modelKey);
|
|
3001
|
+
if (!subscription && !isPriced(modelKey)) fold.unpricedModels.add(modelKey);
|
|
2999
3002
|
const entry = priced ? modelOf(modelKey) : void 0;
|
|
3000
3003
|
const thisCost = entry === void 0 ? void 0 : computeCostAt(entry, {
|
|
3001
3004
|
input: (usage.cacheReadTokens ?? 0) + usage.inputTokens + (usage.cacheWriteTokens ?? 0),
|
|
@@ -3003,13 +3006,13 @@ function foldInto(fold, machine, events, subscriptionProviders, officialProvider
|
|
|
3003
3006
|
cacheMiss: usage.inputTokens + (usage.cacheWriteTokens ?? 0),
|
|
3004
3007
|
output: usage.outputTokens
|
|
3005
3008
|
}, event.time);
|
|
3006
|
-
foldUsage(fold.total, usage, modelKey, subscription, event.time, official, thisCost);
|
|
3007
|
-
foldUsage(usageCell(fold.byModel, modelKey), usage, modelKey, subscription, event.time, official, thisCost);
|
|
3008
|
-
foldUsage(usageCell(fold.byDay, day), usage, modelKey, subscription, event.time, official, thisCost);
|
|
3009
|
-
foldUsage(modelDayCell(fold.byDayModels, day, modelKey), usage, modelKey, subscription, event.time, official, thisCost);
|
|
3010
|
-
foldUsage(modelDaySiteCell(fold.byDayModelsSite, day, modelKey, siteBucket), usage, modelKey, subscription, event.time, official, thisCost);
|
|
3011
|
-
foldUsage(usageCell(fold.bySite, siteBucket), usage, modelKey, subscription, event.time, official, thisCost);
|
|
3012
|
-
foldUsage(usageCell(fold.byTier, tierAt(event.time)), usage, modelKey, subscription, event.time, official, thisCost);
|
|
3009
|
+
foldUsage(fold.total, usage, modelKey, subscription, event.time, official, thisCost, unverifiedChannel);
|
|
3010
|
+
foldUsage(usageCell(fold.byModel, modelKey), usage, modelKey, subscription, event.time, official, thisCost, unverifiedChannel);
|
|
3011
|
+
foldUsage(usageCell(fold.byDay, day), usage, modelKey, subscription, event.time, official, thisCost, unverifiedChannel);
|
|
3012
|
+
foldUsage(modelDayCell(fold.byDayModels, day, modelKey), usage, modelKey, subscription, event.time, official, thisCost, unverifiedChannel);
|
|
3013
|
+
foldUsage(modelDaySiteCell(fold.byDayModelsSite, day, modelKey, siteBucket), usage, modelKey, subscription, event.time, official, thisCost, unverifiedChannel);
|
|
3014
|
+
foldUsage(usageCell(fold.bySite, siteBucket), usage, modelKey, subscription, event.time, official, thisCost, unverifiedChannel);
|
|
3015
|
+
foldUsage(usageCell(fold.byTier, tierAt(event.time)), usage, modelKey, subscription, event.time, official, thisCost, unverifiedChannel);
|
|
3013
3016
|
if (subscription) fold.planCalls.set(modelKey, (fold.planCalls.get(modelKey) ?? 0) + 1);
|
|
3014
3017
|
const turn = event.data.turn ?? -1;
|
|
3015
3018
|
const state = turnState(turns, turn);
|
|
@@ -3207,7 +3210,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
3207
3210
|
id,
|
|
3208
3211
|
...cwd === void 0 ? {} : { cwd },
|
|
3209
3212
|
...stamp === null ? {} : { stamp },
|
|
3210
|
-
foldVersion:
|
|
3213
|
+
foldVersion: 17,
|
|
3211
3214
|
fingerprint: foldFingerprint,
|
|
3212
3215
|
fold: serializeFold(fold)
|
|
3213
3216
|
};
|
|
@@ -3304,7 +3307,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
3304
3307
|
try {
|
|
3305
3308
|
const cwd = meta.cwd;
|
|
3306
3309
|
const ledgerRow = ledger.get(id);
|
|
3307
|
-
if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) ===
|
|
3310
|
+
if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) === 17 && ledgerRow.fingerprint === foldFingerprint) {
|
|
3308
3311
|
const fold = deserializeFold(ledgerRow.fold);
|
|
3309
3312
|
folds.push({
|
|
3310
3313
|
id,
|
|
@@ -3386,7 +3389,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
3386
3389
|
for (const entry of ledger.values()) {
|
|
3387
3390
|
if (included.has(entry.id)) continue;
|
|
3388
3391
|
try {
|
|
3389
|
-
const stale = (entry.foldVersion ?? 1) <
|
|
3392
|
+
const stale = (entry.foldVersion ?? 1) < 17 || entry.fingerprint !== foldFingerprint;
|
|
3390
3393
|
folds.push({
|
|
3391
3394
|
id: entry.id,
|
|
3392
3395
|
...entry.cwd === void 0 ? {} : { cwd: entry.cwd },
|
|
@@ -6409,10 +6412,12 @@ async function queryRelayQuotas(ctx, routes) {
|
|
|
6409
6412
|
/**
|
|
6410
6413
|
* usage-stats 工具开关的共享设置契约(node 与 client 两端共用)。
|
|
6411
6414
|
*
|
|
6412
|
-
*
|
|
6413
|
-
*
|
|
6414
|
-
*
|
|
6415
|
-
*
|
|
6415
|
+
* 旧宿主(≤0.1.6)把该开关存设置命名空间;宿主 0.1.7+ 起注册面移除,改存插件
|
|
6416
|
+
* 条目 config 的 volatile 字段(Config schema 投影),写回经 settings.update,
|
|
6417
|
+
* 提交后引用原位更新、`usage_stats` 工具即时注册/注销(无需重载应用)。
|
|
6418
|
+
* client 半区在「设置」Tab 渲染开关并经 /api/billing/usage-tool 写入,node 半区
|
|
6419
|
+
* 按宿主世代选择通道。缺省的默认行为是关闭——避免该工具默认占用模型每次请求
|
|
6420
|
+
* 的上下文(coding 场景通常在仪表盘看用量)。
|
|
6416
6421
|
*/
|
|
6417
6422
|
/** 设置命名空间 id(小写 kebab-case)。 */
|
|
6418
6423
|
const BILLING_SETTINGS_NAMESPACE = "ui-usage-billing";
|
|
@@ -6523,6 +6528,13 @@ function validateSettingsNamespace(value) {
|
|
|
6523
6528
|
const usageBillingSettingsNs = validateSettingsNamespace(BILLING_SETTINGS_NAMESPACE);
|
|
6524
6529
|
/** 该命名空间的 wire schema:`enableUsageStatsTool` 布尔,默认关闭(issue 诉求)。 */
|
|
6525
6530
|
const UsageBillingSettingsSchema = z.object({ [ENABLE_USAGE_STATS_TOOL_FIELD]: z.boolean().default(false) });
|
|
6531
|
+
/**
|
|
6532
|
+
* Loader 挂载时对条目 config 做校验的 Config 声明:`enableUsageStatsTool` 标记
|
|
6533
|
+
* volatile,宿主 0.1.7+ 设置系统据此把它投影为可实时编辑的开关(describe/update)。
|
|
6534
|
+
* schemastery 非严格 object 透传未声明键,其余 config 字段不受影响;未导出 Config
|
|
6535
|
+
* 的旧宿主直接把原始 config 交给 apply,普通布尔形态由读取端兼容。
|
|
6536
|
+
*/
|
|
6537
|
+
const Config = z.object({ [ENABLE_USAGE_STATS_TOOL_FIELD]: z.boolean().default(false).volatile() });
|
|
6526
6538
|
/** 实时定价的后台刷新间隔(毫秒):汇率/模型价低频变化,6 小时一次足够。 */
|
|
6527
6539
|
const PRICING_REFRESH_INTERVAL_MS = 216e5;
|
|
6528
6540
|
/** 历史回放预热的延迟:等宿主启动高峰(插件加载 / 路由挂载)过去再全量折叠,
|
|
@@ -6872,6 +6884,22 @@ function adaptSessionPersistence(raw) {
|
|
|
6872
6884
|
};
|
|
6873
6885
|
}
|
|
6874
6886
|
/**
|
|
6887
|
+
* 读取 usage_stats 工具开关的当前生效值:宿主 0.1.7+ 经 Config 解析后是 volatile
|
|
6888
|
+
* 引用(.get() 即时反映设置写入,无需重载),直连 apply(测试)与未导出 Config 的
|
|
6889
|
+
* 旧宿主组合是普通布尔;缺省/非法值一律回 DEFAULT。
|
|
6890
|
+
* @param config - apply 收到的插件 config。
|
|
6891
|
+
* @returns 开关当前生效值。
|
|
6892
|
+
*/
|
|
6893
|
+
function readUsageStatsToggle(config) {
|
|
6894
|
+
const field = config[ENABLE_USAGE_STATS_TOOL_FIELD];
|
|
6895
|
+
if (typeof field === "boolean") return field;
|
|
6896
|
+
if (typeof field === "object" && field !== null && typeof field.get === "function") {
|
|
6897
|
+
const current = field.get();
|
|
6898
|
+
return typeof current === "boolean" ? current : false;
|
|
6899
|
+
}
|
|
6900
|
+
return false;
|
|
6901
|
+
}
|
|
6902
|
+
/**
|
|
6875
6903
|
* Host plugin body: serve real aggregated usage to the browser dashboard.
|
|
6876
6904
|
* @param ctx - host context carrying webServer and sessionPersistence.
|
|
6877
6905
|
* @param config - optional statsPath override.
|
|
@@ -6879,6 +6907,7 @@ function adaptSessionPersistence(raw) {
|
|
|
6879
6907
|
function apply(ctx, config = {}) {
|
|
6880
6908
|
const trustedHosts = normalizeTrustedHosts(config.trustedHosts);
|
|
6881
6909
|
let usageSettingsScope;
|
|
6910
|
+
let liveSettings;
|
|
6882
6911
|
const cwd = process.cwd();
|
|
6883
6912
|
const dshHome = resolveDshHome();
|
|
6884
6913
|
const snapshotPath = config.snapshotPath ?? join(dshHome, ".dsh-usage-stats.json");
|
|
@@ -7023,131 +7052,113 @@ function apply(ctx, config = {}) {
|
|
|
7023
7052
|
}
|
|
7024
7053
|
}
|
|
7025
7054
|
}), "usage-billing: notify-claim route");
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
async execute(args, exec) {
|
|
7084
|
-
const stats = await aggregator.aggregate();
|
|
7085
|
-
const zero = {
|
|
7086
|
-
range: args.range,
|
|
7087
|
-
cost: 0,
|
|
7088
|
-
calls: 0,
|
|
7089
|
-
input: 0,
|
|
7090
|
-
output: 0
|
|
7091
|
-
};
|
|
7092
|
-
if (args.range === "all") return {
|
|
7093
|
-
range: args.range,
|
|
7094
|
-
cost: stats.total.cost,
|
|
7095
|
-
calls: stats.total.calls,
|
|
7096
|
-
input: stats.total.input,
|
|
7097
|
-
output: stats.total.output
|
|
7098
|
-
};
|
|
7099
|
-
if (args.range === "bySite" || args.range === "relay") return {
|
|
7100
|
-
range: args.range,
|
|
7101
|
-
...sumSiteBuckets(stats.bySite ?? {}, args.range)
|
|
7102
|
-
};
|
|
7103
|
-
if (args.range === "today") {
|
|
7104
|
-
const day = stats.byDay[dayStamp(Date.now())];
|
|
7105
|
-
return day === void 0 ? zero : {
|
|
7106
|
-
range: args.range,
|
|
7107
|
-
cost: day.cost,
|
|
7108
|
-
calls: day.calls,
|
|
7109
|
-
input: day.input,
|
|
7110
|
-
output: day.output
|
|
7111
|
-
};
|
|
7112
|
-
}
|
|
7113
|
-
if (args.range === "month") {
|
|
7114
|
-
const prefix = dayStamp(Date.now()).slice(0, 7);
|
|
7115
|
-
let cost = 0;
|
|
7116
|
-
let calls = 0;
|
|
7117
|
-
let input = 0;
|
|
7118
|
-
let output = 0;
|
|
7119
|
-
for (const [date, day] of Object.entries(stats.byDay)) {
|
|
7120
|
-
if (!date.startsWith(prefix)) continue;
|
|
7121
|
-
cost += day.cost;
|
|
7122
|
-
calls += day.calls;
|
|
7123
|
-
input += day.input;
|
|
7124
|
-
output += day.output;
|
|
7055
|
+
let usageToolDisposer;
|
|
7056
|
+
let usageToolRegister;
|
|
7057
|
+
const syncUsageTool = () => {
|
|
7058
|
+
if (usageToolRegister === void 0) return;
|
|
7059
|
+
if (readUsageStatsToggle(config)) {
|
|
7060
|
+
if (usageToolDisposer === void 0) usageToolDisposer = usageToolRegister();
|
|
7061
|
+
return;
|
|
7062
|
+
}
|
|
7063
|
+
if (usageToolDisposer !== void 0) {
|
|
7064
|
+
usageToolDisposer();
|
|
7065
|
+
usageToolDisposer = void 0;
|
|
7066
|
+
}
|
|
7067
|
+
};
|
|
7068
|
+
ctx.inject(["tools"], (toolsCtx) => {
|
|
7069
|
+
usageToolRegister = () => toolsCtx.tools.register(defineTool({
|
|
7070
|
+
name: "usage_stats",
|
|
7071
|
+
description: "查询本机 DeepSeek Harness 的模型用量与估算费用(人民币,按官方目录价估算,非账单)。range 取值:today=今天,month=本月,session=当前会话,all=累计。",
|
|
7072
|
+
parameters: { range: {
|
|
7073
|
+
type: "string",
|
|
7074
|
+
enum: [
|
|
7075
|
+
"today",
|
|
7076
|
+
"month",
|
|
7077
|
+
"session",
|
|
7078
|
+
"all",
|
|
7079
|
+
"bySite",
|
|
7080
|
+
"relay"
|
|
7081
|
+
],
|
|
7082
|
+
required: true,
|
|
7083
|
+
description: "统计范围:today / month / session / all / bySite / relay"
|
|
7084
|
+
} },
|
|
7085
|
+
output: {
|
|
7086
|
+
schema: {
|
|
7087
|
+
type: "object",
|
|
7088
|
+
additionalProperties: false,
|
|
7089
|
+
properties: {
|
|
7090
|
+
range: {
|
|
7091
|
+
type: "string",
|
|
7092
|
+
required: true
|
|
7093
|
+
},
|
|
7094
|
+
cost: {
|
|
7095
|
+
type: "number",
|
|
7096
|
+
required: true,
|
|
7097
|
+
description: "估算费用(人民币元)"
|
|
7098
|
+
},
|
|
7099
|
+
calls: {
|
|
7100
|
+
type: "number",
|
|
7101
|
+
required: true
|
|
7102
|
+
},
|
|
7103
|
+
input: {
|
|
7104
|
+
type: "number",
|
|
7105
|
+
required: true,
|
|
7106
|
+
description: "输入 tokens"
|
|
7107
|
+
},
|
|
7108
|
+
output: {
|
|
7109
|
+
type: "number",
|
|
7110
|
+
required: true,
|
|
7111
|
+
description: "输出 tokens"
|
|
7125
7112
|
}
|
|
7126
|
-
return {
|
|
7127
|
-
range: args.range,
|
|
7128
|
-
cost,
|
|
7129
|
-
calls,
|
|
7130
|
-
input,
|
|
7131
|
-
output
|
|
7132
|
-
};
|
|
7133
7113
|
}
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7114
|
+
},
|
|
7115
|
+
render: (_args, value) => [{
|
|
7116
|
+
type: "text",
|
|
7117
|
+
text: `用量(${value.range}):估算费用 ${formatMoney(value.cost)},调用 ${value.calls} 次,输入 ${formatTokens(value.input)} tokens,输出 ${formatTokens(value.output)} tokens`
|
|
7118
|
+
}]
|
|
7119
|
+
},
|
|
7120
|
+
async execute(args, exec) {
|
|
7121
|
+
const stats = await aggregator.aggregate();
|
|
7122
|
+
const zero = {
|
|
7123
|
+
range: args.range,
|
|
7124
|
+
cost: 0,
|
|
7125
|
+
calls: 0,
|
|
7126
|
+
input: 0,
|
|
7127
|
+
output: 0
|
|
7128
|
+
};
|
|
7129
|
+
if (args.range === "all") return {
|
|
7130
|
+
range: args.range,
|
|
7131
|
+
cost: stats.total.cost,
|
|
7132
|
+
calls: stats.total.calls,
|
|
7133
|
+
input: stats.total.input,
|
|
7134
|
+
output: stats.total.output
|
|
7135
|
+
};
|
|
7136
|
+
if (args.range === "bySite" || args.range === "relay") return {
|
|
7137
|
+
range: args.range,
|
|
7138
|
+
...sumSiteBuckets(stats.bySite ?? {}, args.range)
|
|
7139
|
+
};
|
|
7140
|
+
if (args.range === "today") {
|
|
7141
|
+
const day = stats.byDay[dayStamp(Date.now())];
|
|
7142
|
+
return day === void 0 ? zero : {
|
|
7138
7143
|
range: args.range,
|
|
7139
|
-
|
|
7144
|
+
cost: day.cost,
|
|
7145
|
+
calls: day.calls,
|
|
7146
|
+
input: day.input,
|
|
7147
|
+
output: day.output
|
|
7140
7148
|
};
|
|
7149
|
+
}
|
|
7150
|
+
if (args.range === "month") {
|
|
7151
|
+
const prefix = dayStamp(Date.now()).slice(0, 7);
|
|
7141
7152
|
let cost = 0;
|
|
7142
7153
|
let calls = 0;
|
|
7143
7154
|
let input = 0;
|
|
7144
7155
|
let output = 0;
|
|
7145
|
-
for (const
|
|
7146
|
-
if (
|
|
7147
|
-
cost +=
|
|
7148
|
-
calls +=
|
|
7149
|
-
input +=
|
|
7150
|
-
output +=
|
|
7156
|
+
for (const [date, day] of Object.entries(stats.byDay)) {
|
|
7157
|
+
if (!date.startsWith(prefix)) continue;
|
|
7158
|
+
cost += day.cost;
|
|
7159
|
+
calls += day.calls;
|
|
7160
|
+
input += day.input;
|
|
7161
|
+
output += day.output;
|
|
7151
7162
|
}
|
|
7152
7163
|
return {
|
|
7153
7164
|
range: args.range,
|
|
@@ -7157,8 +7168,48 @@ function apply(ctx, config = {}) {
|
|
|
7157
7168
|
output
|
|
7158
7169
|
};
|
|
7159
7170
|
}
|
|
7160
|
-
|
|
7161
|
-
|
|
7171
|
+
const sessionId = exec.agent?.id;
|
|
7172
|
+
if (sessionId === void 0) throw new Error("usage_stats 的 session 范围需要 agent 会话上下文");
|
|
7173
|
+
const summary = await aggregator.sessionUsageOf(String(sessionId));
|
|
7174
|
+
if (summary !== void 0) return {
|
|
7175
|
+
range: args.range,
|
|
7176
|
+
...summary
|
|
7177
|
+
};
|
|
7178
|
+
let cost = 0;
|
|
7179
|
+
let calls = 0;
|
|
7180
|
+
let input = 0;
|
|
7181
|
+
let output = 0;
|
|
7182
|
+
for (const turn of stats.byTurn ?? []) {
|
|
7183
|
+
if (turn.sessionId !== String(sessionId)) continue;
|
|
7184
|
+
cost += turn.cost;
|
|
7185
|
+
calls += 1;
|
|
7186
|
+
input += turn.input;
|
|
7187
|
+
output += turn.output;
|
|
7188
|
+
}
|
|
7189
|
+
return {
|
|
7190
|
+
range: args.range,
|
|
7191
|
+
cost,
|
|
7192
|
+
calls,
|
|
7193
|
+
input,
|
|
7194
|
+
output
|
|
7195
|
+
};
|
|
7196
|
+
}
|
|
7197
|
+
}));
|
|
7198
|
+
syncUsageTool();
|
|
7199
|
+
ctx.on("loader/volatile-update", syncUsageTool);
|
|
7200
|
+
});
|
|
7201
|
+
ctx.inject(["settings"], (sctx) => {
|
|
7202
|
+
const legacy = sctx.settings;
|
|
7203
|
+
if (typeof legacy.register === "function") {
|
|
7204
|
+
usageSettingsScope = legacy.register(usageBillingSettingsNs, UsageBillingSettingsSchema, { base: { enableUsageStatsTool: readUsageStatsToggle(config) } });
|
|
7205
|
+
return;
|
|
7206
|
+
}
|
|
7207
|
+
const forms = legacy;
|
|
7208
|
+
if (typeof forms.update !== "function") return;
|
|
7209
|
+
liveSettings = {
|
|
7210
|
+
update: (ns, patch) => forms.update(ns, patch),
|
|
7211
|
+
ns: ctx.fiber.entry?.options.id ?? usageBillingSettingsNs
|
|
7212
|
+
};
|
|
7162
7213
|
});
|
|
7163
7214
|
let live = { source: "builtin" };
|
|
7164
7215
|
let pricingSyncedAt = 0;
|
|
@@ -7301,7 +7352,7 @@ function apply(ctx, config = {}) {
|
|
|
7301
7352
|
return;
|
|
7302
7353
|
}
|
|
7303
7354
|
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
7304
|
-
const enabled = usageSettingsScope
|
|
7355
|
+
const enabled = usageSettingsScope !== void 0 ? usageSettingsScope.get().enableUsageStatsTool : readUsageStatsToggle(config);
|
|
7305
7356
|
if (req.method === "GET") {
|
|
7306
7357
|
res.end(JSON.stringify({ enabled }));
|
|
7307
7358
|
return;
|
|
@@ -7325,18 +7376,23 @@ function apply(ctx, config = {}) {
|
|
|
7325
7376
|
return;
|
|
7326
7377
|
}
|
|
7327
7378
|
}
|
|
7328
|
-
const
|
|
7329
|
-
if (usageSettingsScope
|
|
7379
|
+
const next = JSON.parse(body === "" ? "{}" : body).enabled === true;
|
|
7380
|
+
if (usageSettingsScope !== void 0) {
|
|
7381
|
+
await usageSettingsScope.update({ enableUsageStatsTool: next });
|
|
7382
|
+
syncUsageTool();
|
|
7383
|
+
} else if (liveSettings !== void 0) {
|
|
7384
|
+
await liveSettings.update(liveSettings.ns, { [ENABLE_USAGE_STATS_TOOL_FIELD]: next });
|
|
7385
|
+
syncUsageTool();
|
|
7386
|
+
} else {
|
|
7330
7387
|
res.end(JSON.stringify({ error: "settings unavailable" }));
|
|
7331
7388
|
return;
|
|
7332
7389
|
}
|
|
7333
|
-
const next = parsed.enabled === true;
|
|
7334
|
-
await usageSettingsScope.update({ enableUsageStatsTool: next });
|
|
7335
7390
|
res.end(JSON.stringify({
|
|
7336
7391
|
ok: true,
|
|
7337
7392
|
enabled: next
|
|
7338
7393
|
}));
|
|
7339
|
-
} catch {
|
|
7394
|
+
} catch (error) {
|
|
7395
|
+
console.warn("[usage-billing] usage-tool toggle write-back failed:", error);
|
|
7340
7396
|
res.end(JSON.stringify({ error: "invalid" }));
|
|
7341
7397
|
}
|
|
7342
7398
|
}
|
|
@@ -7476,4 +7532,4 @@ function apply(ctx, config = {}) {
|
|
|
7476
7532
|
}), "usage-billing: usage-stats route");
|
|
7477
7533
|
}
|
|
7478
7534
|
//#endregion
|
|
7479
|
-
export { adaptSessionPersistence, apply, createFileUsageLedgerStore, guardLoopback, inject, normalizeTrustedHosts, readPiAiProviderRoutes, resolveSubscriptionKeys };
|
|
7535
|
+
export { Config, adaptSessionPersistence, apply, createFileUsageLedgerStore, guardLoopback, inject, normalizeTrustedHosts, readPiAiProviderRoutes, resolveSubscriptionKeys };
|
package/lib/types/aggregate.d.ts
CHANGED
|
@@ -174,8 +174,10 @@ export declare function emptyUsage(): ModelUsage;
|
|
|
174
174
|
* @param pricedCost - 调用方预算好的本次费用(同一条消息折叠进多个桶时计价结果相同,
|
|
175
175
|
* 逐桶重算会让最热路径付出 N 倍常数);提供时跳过内部的计价闸门与计算,
|
|
176
176
|
* 调用方保证其已按 `!subscription && isPriced(key)` 判定。
|
|
177
|
+
* @param unverifiedChannel - 本次调用是否来自未知通道(配置外 provider)。未知通道的
|
|
178
|
+
* 支出主体无法核实,费用一律记 0(token 照常统计,issue #82)。
|
|
177
179
|
*/
|
|
178
|
-
export declare function foldUsage(acc: ModelUsage, usage: TokenUsage, key: string, subscription: boolean, timeMs: number, official?: boolean, pricedCost?: number): void;
|
|
180
|
+
export declare function foldUsage(acc: ModelUsage, usage: TokenUsage, key: string, subscription: boolean, timeMs: number, official?: boolean, pricedCost?: number, unverifiedChannel?: boolean): void;
|
|
179
181
|
/**
|
|
180
182
|
* 联网搜索辅助请求的单次费用估算默认值(人民币元)。DeepSeek 官方对搜索请求
|
|
181
183
|
* (web_search 服务端工具注入上下文)照常计费,实测每次约 0.01~0.03 元,取中值;
|
|
@@ -552,7 +554,7 @@ export interface UsageLedgerDocument {
|
|
|
552
554
|
* 会话费用只剩最近一段,issue #29)。
|
|
553
555
|
* 持久账本行据此区分新旧算法:日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
|
|
554
556
|
*/
|
|
555
|
-
export declare const FOLD_VERSION =
|
|
557
|
+
export declare const FOLD_VERSION = 17;
|
|
556
558
|
/**
|
|
557
559
|
* 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
|
|
558
560
|
* 未来账本/schema 字段变更(重命名、拆桶、语义调整)时,在此追加一条迁移并
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Locale dictionaries for the usage billing surface. */
|
|
2
|
-
export type UsageBillingKey = 'title' | 'cost' | 'todayCost' | 'monthCost' | 'yearCost' | 'monthProjected' | 'liveTurn' | 'liveSession' | 'calls' | 'cacheHitRate' | 'tokens' | 'inputTokens' | 'outputTokens' | 'avgCost' | 'kpiRange' | 'avgRangeToday' | 'avgRange7d' | 'avgRangeWeek' | 'avgRangeMonth' | 'avgRangeAll' | 'trend' | 'trend7d' | 'trend30d' | 'trendMetric' | 'trendMetricCost' | 'trendMetricTokens' | 'pricingSearch' | 'bandPinHint' | 'trendEmpty' | 'trendTotal' | 'roundsUnit' | 'healthModelsLive' | 'healthVendorsDown' | 'healthVendorsUnavailable' | 'budget' | 'budgetRemain' | 'budgetAmount' | 'budgetSummary' | 'sessions' | 'sessionTitle' | 'lastActive' | 'sessionOverflow' | 'budgetTierBody' | 'models' | 'providerBilling' | 'channelUnknown' | 'directTag' | 'relayTag' | 'unknownTag' | 'planCountUnit' | 'actual' | 'pricing' | 'input' | 'output' | 'cacheHit' | 'peak' | 'offPeak' | 'flat' | 'band' | 'close' | 'footer' | 'footerCredit' | 'lastUpdated' | 'noData' | 'todayRate' | 'rateLive' | 'rateBuiltin' | 'pricingSyncedAt' | 'pricingSyncNow' | 'pricingSyncing' | 'promoBadge' | 'promoUntil' | 'promoOpenEnded' | 'pricingTip' | 'pricingUnit' | 'pricingNotes' | 'ubPeak' | 'ubOff' | 'ubStd' | 'peakBand' | 'pricingSource' | 'noteCache' | 'noteBand' | 'noteSource' | 'balance' | 'recharge' | 'rechargeHint' | 'balanceUnconfigured' | 'balanceUnauthorized' | 'balanceUnreachable' | 'uncatalogued' | 'estimatedPricing' | 'balanceDays' | 'balanceLowBody' | 'reconcileDrift' | 'reconcileDismiss' | 'subscriptionNotConfigured' | 'subscriptionUnauthorized' | 'subscriptionUnavailable' | 'subscriptionInvalid' | 'subscriptionRateLimited' | 'subscriptionSession' | 'subscriptionWeekly' | 'subscriptionMonthly' | 'subscriptionBilling' | 'subscriptionRemaining' | 'subscriptionExhausted' | 'subscriptionReset' | 'subscriptionNoApi' | 'floatWindow' | 'floatModeCombined' | 'floatModeSubscription' | 'floatMode' | 'floatTargets' | 'floatWindowHint' | 'floatPrimaryToday' | 'floatPrimaryWeek' | 'floatPrimaryMonth' | 'floatNoTargets' | 'floatNoTargetsHint' | 'cardDisplay' | 'cardDisplayHint' | 'cardMetric' | 'cardMetricMoney' | 'cardMetricTokens' | 'cardSpan' | 'cardSpanDay' | 'cardSpanWeek' | 'cardSpanMonth' | 'triggerMonthTokens' | 'triggerTodayTokens' | 'triggerWeekTokens' | 'subscriptionsStale' | 'staleLedgerNotice' | 'unreadableNotice' | 'tokenCacheWrite' | 'toolRank' | 'toolName' | 'userPrices' | 'userPricesHint' | 'userPriceSave' | 'userPriceModel' | 'userPriceSource' | 'userPriceSourceHint' | 'userPriceOffPeak' | 'userPriceNormal' | 'userPriceCurrency' | 'userPriceAdd' | 'userPriceRemoveSelected' | 'userPriceSelect' | 'sessionStaleBadge' | 'sessionUntitled' | 'heatmapLess' | 'heatmapMore' | 'currency' | 'currencyCny' | 'currencyUsd' | 'currencyEur' | 'language' | 'heatmap' | 'rounds' | 'roundsHint' | 'anomaly' | 'model' | 'thModel' | 'thInputMiss' | 'thInputHit' | 'costAbbr' | 'tabOverview' | 'tabTrends' | 'tabProviders' | 'tabPricing' | 'tabSettings' | 'budgetHint' | 'peakAlertHint' | 'peakAlertDescPeak' | 'peakAlertDescOff' | 'peakAlertDescPeakZhipu' | 'peakAlertDescOffZhipu' | 'export' | 'exportCsvDay' | 'exportCsvSession' | 'exportJson' | 'peakShare' | 'peakSharePerCall' | 'offPeakSavings' | 'perfMax' | 'weekCost' | 'roleCost' | 'roleUser' | 'roleAssistant' | 'roleTool' | 'roleHint' | 'tierPeak' | 'tierOff' | 'tierToPeak' | 'tierToOff' | 'tierAlertEnterPeak' | 'tierAlertEnterOff' | 'tierAlertEnterPeakZhipu' | 'tierAlertEnterOffZhipu' | 'peakAlertTitlePeak' | 'peakAlertTitleOff' | 'peakAlert' | 'peakAlertLeadMin' | 'peakAlertPos' | 'peakAlertMode' | 'peakAlertPosCorner' | 'peakAlertPosCenter' | 'peakAlertModePeak' | 'peakAlertModeOff' | 'peakAlertModeBoth' | 'peakAlertWebNotify' | 'peakAlertPreview' | 'planTypeCode' | 'planTypeToken' | 'subscriptionFeePerMonth' | 'triggerToday' | 'triggerMonth' | 'subscriptionTag' | 'free' | 'perfSamples' | 'perfTtft' | 'perfP50' | 'perfP90' | 'perfTps' | 'perfLatency' | 'perfEstimated' | 'perfEmpty' | 'perfTpsUnit' | 'perfTitle' | 'perfHint' | 'perfAll' | 'perfChartEmpty' | 'heatmapYear' | 'heatmapHalf' | 'heatmapMonth' | 'metricCost' | 'metricTokens' | 'heatmapTotal' | 'activeDays' | 'streakDays' | 'subscriptionAutoDetect' | 'pluginAuthor' | 'pluginRepository' | 'pluginNpm' | 'pluginLicense' | 'tabToken' | 'tokenExport' | 'tokenExportCsv' | 'tokenReasoningShare' | 'tokenReasoningShort' | 'tokenIo' | 'tokenPeak' | 'tokenDaily' | 'tokenByModel' | 'tokenMiss' | 'tokenHit' | 'tokenOutput' | 'tokenViewStructure' | 'tokenViewModel' | 'tokenHitShort' | 'tokenMissShort' | 'tokenTotal' | 'tokenShare' | 'usageStatsTool' | 'usageStatsToolHint' | 'balanceGranted' | 'balanceTopped' | 'balanceDaily' | 'balanceDaysLong' | 'balanceDaysUnit' | 'popTodayModel' | 'popNoConsumption' | 'popTitle' | 'popDirectLead' | 'popSubLead' | 'unpricedHint' | 'searchEstimateHint' | 'siteListDisplay' | 'siteListDisplayHint' | 'liveCostBar' | 'liveCostBarHint' | 'liveCostBarPosition' | 'liveCostBarPosBelow' | 'liveCostBarPosAbove' | 'liveCostBarPosToolbar' | 'exportCsvSite' | 'panelRelayQuota' | 'relayBalance' | 'relayNoQuota' | 'relayWindowUsed' | 'relayKindNewApi' | 'relayKindSub2Api' | 'relayKindUnknown' | 'expandAll' | 'collapseAll' | 'todayOnly' | 'todayNoConsumption';
|
|
2
|
+
export type UsageBillingKey = 'title' | 'cost' | 'todayCost' | 'monthCost' | 'yearCost' | 'monthProjected' | 'liveTurn' | 'liveSession' | 'calls' | 'cacheHitRate' | 'tokens' | 'inputTokens' | 'outputTokens' | 'avgCost' | 'kpiRange' | 'avgRangeToday' | 'avgRange7d' | 'avgRangeWeek' | 'avgRangeMonth' | 'avgRangeAll' | 'trend' | 'trend7d' | 'trend30d' | 'trendMetric' | 'trendMetricCost' | 'trendMetricTokens' | 'pricingSearch' | 'bandPinHint' | 'trendEmpty' | 'trendTotal' | 'roundsUnit' | 'healthModelsLive' | 'healthVendorsDown' | 'healthVendorsUnavailable' | 'budget' | 'budgetRemain' | 'budgetAmount' | 'budgetSummary' | 'sessions' | 'sessionTitle' | 'lastActive' | 'sessionOverflow' | 'budgetTierBody' | 'models' | 'providerBilling' | 'channelUnknown' | 'directTag' | 'relayTag' | 'unknownTag' | 'unknownChannelHint' | 'planCountUnit' | 'actual' | 'pricing' | 'input' | 'output' | 'cacheHit' | 'peak' | 'offPeak' | 'flat' | 'band' | 'close' | 'footer' | 'footerCredit' | 'lastUpdated' | 'noData' | 'todayRate' | 'rateLive' | 'rateBuiltin' | 'pricingSyncedAt' | 'pricingSyncNow' | 'pricingSyncing' | 'promoBadge' | 'promoUntil' | 'promoOpenEnded' | 'pricingTip' | 'pricingUnit' | 'pricingNotes' | 'ubPeak' | 'ubOff' | 'ubStd' | 'peakBand' | 'pricingSource' | 'noteCache' | 'noteBand' | 'noteSource' | 'balance' | 'recharge' | 'rechargeHint' | 'balanceUnconfigured' | 'balanceUnauthorized' | 'balanceUnreachable' | 'uncatalogued' | 'estimatedPricing' | 'balanceDays' | 'balanceLowBody' | 'reconcileDrift' | 'reconcileDismiss' | 'subscriptionNotConfigured' | 'subscriptionUnauthorized' | 'subscriptionUnavailable' | 'subscriptionInvalid' | 'subscriptionRateLimited' | 'subscriptionSession' | 'subscriptionWeekly' | 'subscriptionMonthly' | 'subscriptionBilling' | 'subscriptionRemaining' | 'subscriptionExhausted' | 'subscriptionReset' | 'subscriptionNoApi' | 'floatWindow' | 'floatModeCombined' | 'floatModeSubscription' | 'floatMode' | 'floatTargets' | 'floatWindowHint' | 'floatPrimaryToday' | 'floatPrimaryWeek' | 'floatPrimaryMonth' | 'floatNoTargets' | 'floatNoTargetsHint' | 'cardDisplay' | 'cardDisplayHint' | 'cardMetric' | 'cardMetricMoney' | 'cardMetricTokens' | 'cardSpan' | 'cardSpanDay' | 'cardSpanWeek' | 'cardSpanMonth' | 'triggerMonthTokens' | 'triggerTodayTokens' | 'triggerWeekTokens' | 'subscriptionsStale' | 'staleLedgerNotice' | 'unreadableNotice' | 'tokenCacheWrite' | 'toolRank' | 'toolName' | 'userPrices' | 'userPricesHint' | 'userPriceSave' | 'userPriceModel' | 'userPriceSource' | 'userPriceSourceHint' | 'userPriceOffPeak' | 'userPriceNormal' | 'userPriceCurrency' | 'userPriceAdd' | 'userPriceRemoveSelected' | 'userPriceSelect' | 'sessionStaleBadge' | 'sessionUntitled' | 'heatmapLess' | 'heatmapMore' | 'currency' | 'currencyCny' | 'currencyUsd' | 'currencyEur' | 'language' | 'heatmap' | 'rounds' | 'roundsHint' | 'anomaly' | 'model' | 'thModel' | 'thInputMiss' | 'thInputHit' | 'costAbbr' | 'tabOverview' | 'tabTrends' | 'tabProviders' | 'tabPricing' | 'tabSettings' | 'budgetHint' | 'peakAlertHint' | 'peakAlertDescPeak' | 'peakAlertDescOff' | 'peakAlertDescPeakZhipu' | 'peakAlertDescOffZhipu' | 'export' | 'exportCsvDay' | 'exportCsvSession' | 'exportJson' | 'peakShare' | 'peakSharePerCall' | 'offPeakSavings' | 'perfMax' | 'weekCost' | 'roleCost' | 'roleUser' | 'roleAssistant' | 'roleTool' | 'roleHint' | 'tierPeak' | 'tierOff' | 'tierToPeak' | 'tierToOff' | 'tierAlertEnterPeak' | 'tierAlertEnterOff' | 'tierAlertEnterPeakZhipu' | 'tierAlertEnterOffZhipu' | 'peakAlertTitlePeak' | 'peakAlertTitleOff' | 'peakAlert' | 'peakAlertLeadMin' | 'peakAlertPos' | 'peakAlertMode' | 'peakAlertPosCorner' | 'peakAlertPosCenter' | 'peakAlertModePeak' | 'peakAlertModeOff' | 'peakAlertModeBoth' | 'peakAlertWebNotify' | 'peakAlertPreview' | 'planTypeCode' | 'planTypeToken' | 'subscriptionFeePerMonth' | 'triggerToday' | 'triggerMonth' | 'subscriptionTag' | 'free' | 'perfSamples' | 'perfTtft' | 'perfP50' | 'perfP90' | 'perfTps' | 'perfLatency' | 'perfEstimated' | 'perfEmpty' | 'perfTpsUnit' | 'perfTitle' | 'perfHint' | 'perfAll' | 'perfChartEmpty' | 'heatmapYear' | 'heatmapHalf' | 'heatmapMonth' | 'metricCost' | 'metricTokens' | 'heatmapTotal' | 'activeDays' | 'streakDays' | 'subscriptionAutoDetect' | 'pluginAuthor' | 'pluginRepository' | 'pluginNpm' | 'pluginLicense' | 'tabToken' | 'tokenExport' | 'tokenExportCsv' | 'tokenReasoningShare' | 'tokenReasoningShort' | 'tokenIo' | 'tokenPeak' | 'tokenDaily' | 'tokenByModel' | 'tokenMiss' | 'tokenHit' | 'tokenOutput' | 'tokenViewStructure' | 'tokenViewModel' | 'tokenHitShort' | 'tokenMissShort' | 'tokenTotal' | 'tokenShare' | 'usageStatsTool' | 'usageStatsToolHint' | 'balanceGranted' | 'balanceTopped' | 'balanceDaily' | 'balanceDaysLong' | 'balanceDaysUnit' | 'popTodayModel' | 'popNoConsumption' | 'popTitle' | 'popDirectLead' | 'popSubLead' | 'unpricedHint' | 'searchEstimateHint' | 'siteListDisplay' | 'siteListDisplayHint' | 'liveCostBar' | 'liveCostBarHint' | 'liveCostBarPosition' | 'liveCostBarPosBelow' | 'liveCostBarPosAbove' | 'liveCostBarPosToolbar' | 'exportCsvSite' | 'panelRelayQuota' | 'relayBalance' | 'relayNoQuota' | 'relayWindowUsed' | 'relayKindNewApi' | 'relayKindSub2Api' | 'relayKindUnknown' | 'expandAll' | 'collapseAll' | 'todayOnly' | 'todayNoConsumption';
|
|
3
3
|
export declare const NS = "usageBilling";
|
|
4
4
|
export declare const zh: Record<UsageBillingKey, string>;
|
|
5
5
|
export declare const en: Record<UsageBillingKey, string>;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* usage-stats 工具开关的共享设置契约(node 与 client 两端共用)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* 旧宿主(≤0.1.6)把该开关存设置命名空间;宿主 0.1.7+ 起注册面移除,改存插件
|
|
5
|
+
* 条目 config 的 volatile 字段(Config schema 投影),写回经 settings.update,
|
|
6
|
+
* 提交后引用原位更新、`usage_stats` 工具即时注册/注销(无需重载应用)。
|
|
7
|
+
* client 半区在「设置」Tab 渲染开关并经 /api/billing/usage-tool 写入,node 半区
|
|
8
|
+
* 按宿主世代选择通道。缺省的默认行为是关闭——避免该工具默认占用模型每次请求
|
|
9
|
+
* 的上下文(coding 场景通常在仪表盘看用量)。
|
|
8
10
|
*/
|
|
9
11
|
import type { CostCurrency, UserPriceEntry } from './pricing.ts';
|
|
10
12
|
/** 设置命名空间 id(小写 kebab-case)。 */
|
package/lib/types/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
16
16
|
import type { Context } from '@deepseek-ai/cordis';
|
|
17
17
|
import type { CredentialProvider } from '@deepseek-ai/dsh-credentials';
|
|
18
|
+
import z from '@deepseek-ai/schemastery';
|
|
18
19
|
import { type UsageLedgerStore, type UsagePersistence } from './aggregate.ts';
|
|
19
20
|
import type { CustomBalanceConfig, DeclaredEndpointConfig, SubscriptionPlanConfig } from './pricing-shared.ts';
|
|
20
21
|
import { type IdentifiedSubscriptionPlan, type SubscriptionKeys } from './subscriptions.ts';
|
|
@@ -42,6 +43,23 @@ export declare function normalizeTrustedHosts(hosts: readonly string[] | undefin
|
|
|
42
43
|
* @returns 是否放行;false = 已拒绝并结束响应。
|
|
43
44
|
*/
|
|
44
45
|
export declare function guardLoopback(req: IncomingMessage, res: ServerResponse, trustedHosts?: ReadonlySet<string>): boolean;
|
|
46
|
+
/** 宿主 0.1.7+ 经 Config schema 解析后,volatile 字段在 config 中的稳定引用形态
|
|
47
|
+
* (cosmokit `Volatile<T>` 的最小结构镜像,避免为类型引入直接依赖)。 */
|
|
48
|
+
export interface VolatileToggle {
|
|
49
|
+
/** @returns 当前生效的布尔快照(设置写入后原位更新,无需重载插件)。 */
|
|
50
|
+
get(): boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Loader 挂载时对条目 config 做校验的 Config 声明:`enableUsageStatsTool` 标记
|
|
54
|
+
* volatile,宿主 0.1.7+ 设置系统据此把它投影为可实时编辑的开关(describe/update)。
|
|
55
|
+
* schemastery 非严格 object 透传未声明键,其余 config 字段不受影响;未导出 Config
|
|
56
|
+
* 的旧宿主直接把原始 config 交给 apply,普通布尔形态由读取端兼容。
|
|
57
|
+
*/
|
|
58
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
59
|
+
enableUsageStatsTool: z<boolean, boolean, "volatile-defined">;
|
|
60
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
61
|
+
enableUsageStatsTool: z<boolean, boolean, "volatile-defined">;
|
|
62
|
+
}>>, "plain">;
|
|
45
63
|
/** Plugin configuration. */
|
|
46
64
|
export interface UsageBillingConfig {
|
|
47
65
|
/** Absolute path to a `.dsh-usage-stats.json` fallback file. */
|
|
@@ -83,9 +101,10 @@ export interface UsageBillingConfig {
|
|
|
83
101
|
* 例:`['llm.example.com']`。
|
|
84
102
|
*/
|
|
85
103
|
trustedHosts?: string[];
|
|
86
|
-
/** `usage_stats`
|
|
87
|
-
*
|
|
88
|
-
|
|
104
|
+
/** `usage_stats` 工具注入开关(默认 false:不注入)。宿主 0.1.7+ 经 Config
|
|
105
|
+
* 解析后是 volatile 引用(设置写入即时反映),直连 apply(测试)是普通布尔;
|
|
106
|
+
* 读取统一走 readUsageStatsToggle。该工具占用每次请求的上下文,coding 场景多在仪表盘查看。 */
|
|
107
|
+
enableUsageStatsTool?: boolean | VolatileToggle;
|
|
89
108
|
/**
|
|
90
109
|
* 联网搜索请求(`web/deepseek-search-llm-request`,日志只有请求、无用量事件)
|
|
91
110
|
* 的单次费用估算(人民币元)。这类调用直连官方 api.deepseek.com,开放平台照常
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenz1117/dsh-ui-usage-billing",
|
|
3
3
|
"description": "Usage billing dashboard for DeepSeek Harness: sidebar cost metrics plus a full dashboard modal, priced from a current multi-provider catalog with real usage aggregated from session logs.",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.20",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"deepseek",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"devDependencies": {
|
|
120
120
|
"@deepseek-ai/cordis": "*",
|
|
121
121
|
"@deepseek-ai/cordis-plugin-include": "^1.0.7",
|
|
122
|
-
"@deepseek-ai/cordis-plugin-loader": "^1.0.
|
|
122
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.5",
|
|
123
123
|
"@deepseek-ai/dsh-api-gateway": "0.1.7-rc.1",
|
|
124
124
|
"@deepseek-ai/dsh-api-remotes": "0.1.7-rc.1",
|
|
125
125
|
"@deepseek-ai/dsh-api-session-controller": "0.1.7-rc.1",
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
"@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.1",
|
|
150
150
|
"@deepseek-ai/dsh-user-approval": "0.1.7-rc.1",
|
|
151
151
|
"@deepseek-ai/dsh-util-workspace-path": "0.1.7-rc.1",
|
|
152
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
152
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
153
153
|
"@shikijs/langs": "^4.3.1",
|
|
154
154
|
"@testing-library/dom": "^10.4.1",
|
|
155
155
|
"@testing-library/react": "^16.1.0",
|