@kenz1117/dsh-ui-usage-billing 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +5 -1
- package/README.md +8 -3
- package/lib/client.js +5 -5
- package/lib/index.js +215 -0
- package/lib/types/balance.d.ts +20 -0
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/pricing.d.ts +36 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
|
7
7
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
8
8
|
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
9
9
|
import z from "@deepseek-ai/schemastery";
|
|
10
|
+
import { createHash, createHmac } from "node:crypto";
|
|
10
11
|
//#region lib/types/client/plan-knowledge.js
|
|
11
12
|
/**
|
|
12
13
|
* Plan-knowledge reference (adapted from dsh-spend's `knowledge.js`, MIT):
|
|
@@ -2914,6 +2915,26 @@ const QUERIERS = [
|
|
|
2914
2915
|
route: "zai-coding-cn",
|
|
2915
2916
|
displayName: "智谱 AI",
|
|
2916
2917
|
querier: queryZhipu
|
|
2918
|
+
},
|
|
2919
|
+
{
|
|
2920
|
+
route: "tencent-tokenhub",
|
|
2921
|
+
displayName: "腾讯云 TokenHub",
|
|
2922
|
+
querier: queryTencentTokenPlan
|
|
2923
|
+
},
|
|
2924
|
+
{
|
|
2925
|
+
route: "tokenhub",
|
|
2926
|
+
displayName: "腾讯云 TokenHub",
|
|
2927
|
+
querier: queryTencentTokenPlan
|
|
2928
|
+
},
|
|
2929
|
+
{
|
|
2930
|
+
route: "tencent",
|
|
2931
|
+
displayName: "腾讯云 TokenHub",
|
|
2932
|
+
querier: queryTencentTokenPlan
|
|
2933
|
+
},
|
|
2934
|
+
{
|
|
2935
|
+
route: "tencentcloud",
|
|
2936
|
+
displayName: "腾讯云 TokenHub",
|
|
2937
|
+
querier: queryTencentTokenPlan
|
|
2917
2938
|
}
|
|
2918
2939
|
];
|
|
2919
2940
|
/**
|
|
@@ -2946,6 +2967,200 @@ async function queryBalances(ctx, providers) {
|
|
|
2946
2967
|
return querier(ctx, env);
|
|
2947
2968
|
}));
|
|
2948
2969
|
}
|
|
2970
|
+
/** TokenHub 管控面 API 端点(cloud.tencent.cn/document/api/1823/132270)。 */
|
|
2971
|
+
const TOKENHUB_HOST = "tokenhub.tencentcloudapi.com";
|
|
2972
|
+
/** 云 API 3.0 产品名与版本(签名的 service 段与请求头都必须一致)。 */
|
|
2973
|
+
const TOKENHUB_SERVICE = "tokenhub";
|
|
2974
|
+
const TOKENHUB_VERSION = "2026-03-22";
|
|
2975
|
+
/** 请求地域:管控面对地域不敏感,取默认国内地域(文档地域列表含 ap-guangzhou)。 */
|
|
2976
|
+
const TOKENHUB_REGION = "ap-guangzhou";
|
|
2977
|
+
/** 腾讯云凭据引用值格式:`<SecretId>:<SecretKey>`(分隔符取首个冒号)。 */
|
|
2978
|
+
function parseTencentCredential(value) {
|
|
2979
|
+
const sep = value.indexOf(":");
|
|
2980
|
+
if (sep === -1) return void 0;
|
|
2981
|
+
const secretId = value.slice(0, sep).trim();
|
|
2982
|
+
const secretKey = value.slice(sep + 1).trim();
|
|
2983
|
+
if (secretId === "" || secretKey === "") return void 0;
|
|
2984
|
+
return {
|
|
2985
|
+
secretId,
|
|
2986
|
+
secretKey
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
/**
|
|
2990
|
+
* 构造云 API 3.0 TC3-HMAC-SHA256 签名(官方签名方法 v3)。导出供测试:纯函数,
|
|
2991
|
+
* 输入确定则签名确定。Action 不参与签名——它走 `X-TC-Action` 请求头。
|
|
2992
|
+
* @param secretId - 云 API SecretId。
|
|
2993
|
+
* @param secretKey - 云 API SecretKey。
|
|
2994
|
+
* @param payload - 已序列化的请求体(含 Action/Version/Region 公共参数)。
|
|
2995
|
+
* @param timestamp - 签名时间戳(秒)。
|
|
2996
|
+
* @returns Authorization 头的值。
|
|
2997
|
+
*/
|
|
2998
|
+
function tc3Authorization(secretId, secretKey, payload, timestamp) {
|
|
2999
|
+
const date = (/* @__PURE__ */ new Date(timestamp * 1e3)).toISOString().slice(0, 10);
|
|
3000
|
+
const canonicalRequest = `POST\n/\n\n${`content-type:application/json; charset=utf-8\nhost:${TOKENHUB_HOST}\n`}\ncontent-type;host\n${createHash("sha256").update(payload).digest("hex")}`;
|
|
3001
|
+
const hashedCanonical = createHash("sha256").update(canonicalRequest).digest("hex");
|
|
3002
|
+
const stringToSign = `TC3-HMAC-SHA256\n${String(timestamp)}\n${date}/${TOKENHUB_SERVICE}/tc3_request\n${hashedCanonical}`;
|
|
3003
|
+
return `TC3-HMAC-SHA256 Credential=${secretId}/${date}/${TOKENHUB_SERVICE}/tc3_request, SignedHeaders=content-type;host, Signature=${createHmac("sha256", createHmac("sha256", createHmac("sha256", createHmac("sha256", date).update(secretKey).digest()).update(TOKENHUB_SERVICE).digest()).update("tc3_request").digest()).update(stringToSign).digest("hex")}`;
|
|
3004
|
+
}
|
|
3005
|
+
/** 调用一次 TokenHub 管控面接口:TC3 签名 + 超时保护,返回响应 JSON 的 `Response`。 */
|
|
3006
|
+
async function callTokenHub(secretId, secretKey, action, params) {
|
|
3007
|
+
const payload = JSON.stringify({
|
|
3008
|
+
Action: action,
|
|
3009
|
+
Version: TOKENHUB_VERSION,
|
|
3010
|
+
Region: TOKENHUB_REGION,
|
|
3011
|
+
...params
|
|
3012
|
+
});
|
|
3013
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
3014
|
+
const controller = new AbortController();
|
|
3015
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS$3);
|
|
3016
|
+
try {
|
|
3017
|
+
const response = await fetch(`https://${TOKENHUB_HOST}/`, {
|
|
3018
|
+
method: "POST",
|
|
3019
|
+
headers: {
|
|
3020
|
+
"content-type": "application/json; charset=utf-8",
|
|
3021
|
+
host: TOKENHUB_HOST,
|
|
3022
|
+
"x-tc-action": action.toLowerCase(),
|
|
3023
|
+
"x-tc-version": TOKENHUB_VERSION,
|
|
3024
|
+
"x-tc-region": TOKENHUB_REGION,
|
|
3025
|
+
"x-tc-timestamp": String(timestamp),
|
|
3026
|
+
authorization: tc3Authorization(secretId, secretKey, payload, timestamp)
|
|
3027
|
+
},
|
|
3028
|
+
body: payload,
|
|
3029
|
+
signal: controller.signal
|
|
3030
|
+
});
|
|
3031
|
+
if (response.status === 401 || response.status === 403) throw Object.assign(/* @__PURE__ */ new Error("unauthorized"), { code: "unauthorized" });
|
|
3032
|
+
if (!response.ok) throw Object.assign(/* @__PURE__ */ new Error(`HTTP ${String(response.status)}`), {
|
|
3033
|
+
httpStatus: response.status,
|
|
3034
|
+
code: "unreachable"
|
|
3035
|
+
});
|
|
3036
|
+
const inner = (await response.json()).Response;
|
|
3037
|
+
if (inner === void 0) throw Object.assign(/* @__PURE__ */ new Error("no Response envelope"), { code: "invalid" });
|
|
3038
|
+
if (inner.Error !== void 0 && inner.Error !== null) {
|
|
3039
|
+
const err = inner.Error;
|
|
3040
|
+
const code = err.Code === "AuthFailure.SignatureFailure" || err.Code === "AuthFailure.SecretIdNotFound" ? "unauthorized" : "unreachable";
|
|
3041
|
+
throw Object.assign(new Error(String(err.Code ?? "api-error")), { code });
|
|
3042
|
+
}
|
|
3043
|
+
return inner;
|
|
3044
|
+
} finally {
|
|
3045
|
+
clearTimeout(timer);
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
/**
|
|
3049
|
+
* 在套餐余量对象里防御性提取「剩余额度」:官方 SubPackageBalance/PackageInfo
|
|
3050
|
+
* 的字段名未稳定公开(issue #18 调研期),按语义键名扫描——命中 remaining /
|
|
3051
|
+
* balance / left 语义键直接用;命中 total 与 used 则相减推导。数字一律经
|
|
3052
|
+
* {@link toNumber} 归一化(上游可能给字符串)。
|
|
3053
|
+
* 导出供测试:纯函数。
|
|
3054
|
+
* @param source - 套餐详情里的余量对象(PackageInfo / SubPackageBalance 等)。
|
|
3055
|
+
* @returns 剩余额度(上游单位,通常为 token 数或元);提取不到返回 undefined。
|
|
3056
|
+
*/
|
|
3057
|
+
function pickRemainingQuota(source) {
|
|
3058
|
+
if (source === null || typeof source !== "object") return void 0;
|
|
3059
|
+
const numeric = Object.entries(source).filter(([, v]) => v !== null && toNumber$1(v) !== void 0);
|
|
3060
|
+
const byKey = (needles) => {
|
|
3061
|
+
for (const [key, value] of numeric) {
|
|
3062
|
+
const lower = key.toLowerCase();
|
|
3063
|
+
if (needles.some((n) => lower.includes(n))) {
|
|
3064
|
+
const num = toNumber$1(value);
|
|
3065
|
+
if (num !== void 0 && num >= 0) return num;
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
};
|
|
3069
|
+
const remaining = byKey([
|
|
3070
|
+
"remain",
|
|
3071
|
+
"balance",
|
|
3072
|
+
"left",
|
|
3073
|
+
"available"
|
|
3074
|
+
]);
|
|
3075
|
+
if (remaining !== void 0) return remaining;
|
|
3076
|
+
const total = byKey(["total"]);
|
|
3077
|
+
const used = byKey(["used", "consume"]);
|
|
3078
|
+
if (total !== void 0 && used !== void 0) return Math.max(0, total - used);
|
|
3079
|
+
}
|
|
3080
|
+
/** 套餐列表里提取第一个启用套餐的 TeamId:集合字段名做候选兼容。 */
|
|
3081
|
+
function firstEnabledTeamId(inner) {
|
|
3082
|
+
const candidates = inner.TeamSet ?? inner.TokenPlanSet ?? inner.PlanSet;
|
|
3083
|
+
if (!Array.isArray(candidates)) return void 0;
|
|
3084
|
+
for (const item of candidates) {
|
|
3085
|
+
if (item === null || typeof item !== "object") continue;
|
|
3086
|
+
const row = item;
|
|
3087
|
+
if (row.TeamId === void 0 && row.PlanId === void 0) continue;
|
|
3088
|
+
if (row.Status !== void 0 && row.Status !== "enable") continue;
|
|
3089
|
+
const id = row.TeamId ?? row.PlanId;
|
|
3090
|
+
if (typeof id === "string" && id !== "") return id;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
/**
|
|
3094
|
+
* 查询腾讯云 TokenHub Token Plan 套餐余量。凭据值格式 `<SecretId>:<SecretKey>`
|
|
3095
|
+
* (云 API 密钥,非 TokenHub 推理 key)。链路:套餐列表取 TeamId → 套餐详情读
|
|
3096
|
+
* 主额度包余量。管控面字段名未完全稳定,解析按语义键防御提取。
|
|
3097
|
+
* @param ctx - host context carrying the credentials seam.
|
|
3098
|
+
* @param apiKeyEnv - credential reference resolving the `<SecretId>:<SecretKey>` pair.
|
|
3099
|
+
*/
|
|
3100
|
+
async function queryTencentTokenPlan(ctx, apiKeyEnv) {
|
|
3101
|
+
const provider = "腾讯云 TokenHub";
|
|
3102
|
+
const hit = await ctx.credentials.resolve(credentialRef(apiKeyEnv));
|
|
3103
|
+
if (hit === void 0) return {
|
|
3104
|
+
provider,
|
|
3105
|
+
displayName: provider,
|
|
3106
|
+
error: "unconfigured"
|
|
3107
|
+
};
|
|
3108
|
+
if (!balanceGate.check(provider)) return {
|
|
3109
|
+
provider,
|
|
3110
|
+
displayName: provider,
|
|
3111
|
+
error: "unreachable"
|
|
3112
|
+
};
|
|
3113
|
+
const credential = parseTencentCredential(hit.value);
|
|
3114
|
+
if (credential === void 0) return {
|
|
3115
|
+
provider,
|
|
3116
|
+
displayName: provider,
|
|
3117
|
+
error: "unauthorized"
|
|
3118
|
+
};
|
|
3119
|
+
const doRequest = async () => {
|
|
3120
|
+
const teamId = firstEnabledTeamId(await callTokenHub(credential.secretId, credential.secretKey, "DescribeTokenPlanList", {}));
|
|
3121
|
+
if (teamId === void 0) return {
|
|
3122
|
+
provider,
|
|
3123
|
+
displayName: provider,
|
|
3124
|
+
error: "invalid"
|
|
3125
|
+
};
|
|
3126
|
+
const detail = await callTokenHub(credential.secretId, credential.secretKey, "DescribeTokenPlan", { TeamId: teamId });
|
|
3127
|
+
const remaining = pickRemainingQuota(detail.PackageInfo) ?? pickRemainingQuota(detail);
|
|
3128
|
+
if (remaining === void 0) {
|
|
3129
|
+
console.warn(`[usage-billing] balance response drifted for ${provider}: no remaining-quota field parsed`);
|
|
3130
|
+
return {
|
|
3131
|
+
provider,
|
|
3132
|
+
displayName: provider,
|
|
3133
|
+
error: "invalid"
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
const plan = typeof detail.Name === "string" ? detail.Name : void 0;
|
|
3137
|
+
const exhausted = detail.StopReason === "EXHAUSTED";
|
|
3138
|
+
return {
|
|
3139
|
+
provider,
|
|
3140
|
+
displayName: provider,
|
|
3141
|
+
currency: "CNY",
|
|
3142
|
+
totalBalance: remaining,
|
|
3143
|
+
...plan !== void 0 ? { plan } : {},
|
|
3144
|
+
...exhausted ? { isAvailable: false } : {}
|
|
3145
|
+
};
|
|
3146
|
+
};
|
|
3147
|
+
try {
|
|
3148
|
+
const row = await withRetry(doRequest, {
|
|
3149
|
+
retries: 1,
|
|
3150
|
+
baseDelayMs: 250,
|
|
3151
|
+
maxDelayMs: 2e3
|
|
3152
|
+
});
|
|
3153
|
+
balanceGate.success(provider);
|
|
3154
|
+
return row;
|
|
3155
|
+
} catch (error) {
|
|
3156
|
+
balanceGate.fail(provider);
|
|
3157
|
+
return {
|
|
3158
|
+
provider,
|
|
3159
|
+
displayName: provider,
|
|
3160
|
+
error: error.code === "unauthorized" ? "unauthorized" : "unreachable"
|
|
3161
|
+
};
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
2949
3164
|
/** 点路径取值:`data.total_available` → 逐层下钻;任一缺失返回 undefined。 */
|
|
2950
3165
|
function getPath(data, path) {
|
|
2951
3166
|
let cursor = data;
|
package/lib/types/balance.d.ts
CHANGED
|
@@ -25,6 +25,26 @@ import type { CustomBalanceConfig, CustomBalanceExtract, ProviderBalance } from
|
|
|
25
25
|
export declare function queryBalances(ctx: Context, providers: Readonly<Record<string, {
|
|
26
26
|
apiKeyEnv?: string;
|
|
27
27
|
}>>): Promise<readonly ProviderBalance[]>;
|
|
28
|
+
/**
|
|
29
|
+
* 构造云 API 3.0 TC3-HMAC-SHA256 签名(官方签名方法 v3)。导出供测试:纯函数,
|
|
30
|
+
* 输入确定则签名确定。Action 不参与签名——它走 `X-TC-Action` 请求头。
|
|
31
|
+
* @param secretId - 云 API SecretId。
|
|
32
|
+
* @param secretKey - 云 API SecretKey。
|
|
33
|
+
* @param payload - 已序列化的请求体(含 Action/Version/Region 公共参数)。
|
|
34
|
+
* @param timestamp - 签名时间戳(秒)。
|
|
35
|
+
* @returns Authorization 头的值。
|
|
36
|
+
*/
|
|
37
|
+
export declare function tc3Authorization(secretId: string, secretKey: string, payload: string, timestamp: number): string;
|
|
38
|
+
/**
|
|
39
|
+
* 在套餐余量对象里防御性提取「剩余额度」:官方 SubPackageBalance/PackageInfo
|
|
40
|
+
* 的字段名未稳定公开(issue #18 调研期),按语义键名扫描——命中 remaining /
|
|
41
|
+
* balance / left 语义键直接用;命中 total 与 used 则相减推导。数字一律经
|
|
42
|
+
* {@link toNumber} 归一化(上游可能给字符串)。
|
|
43
|
+
* 导出供测试:纯函数。
|
|
44
|
+
* @param source - 套餐详情里的余量对象(PackageInfo / SubPackageBalance 等)。
|
|
45
|
+
* @returns 剩余额度(上游单位,通常为 token 数或元);提取不到返回 undefined。
|
|
46
|
+
*/
|
|
47
|
+
export declare function pickRemainingQuota(source: unknown): number | undefined;
|
|
28
48
|
/**
|
|
29
49
|
* 按 extract 规则从响应 JSON 求值。导出供测试:纯函数。
|
|
30
50
|
* @param rule - 提取规则(const / path / add / subtract / divide)。
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Locale dictionaries for the usage billing surface. */
|
|
2
|
-
export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.monthCost' | 'billing.yearCost' | 'billing.monthProjected' | 'billing.liveTurn' | 'billing.liveSession' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trend7d' | 'billing.trend30d' | 'billing.trendMetric' | 'billing.trendMetricCost' | 'billing.trendMetricTokens' | 'billing.trendEmpty' | 'billing.budget' | 'billing.budgetAmount' | 'billing.budgetSummary' | 'billing.sessions' | 'billing.sessionTitle' | 'billing.project' | 'billing.lastActive' | 'billing.sessionOverflow' | 'billing.budgetTierBody' | 'billing.models' | 'billing.providerBilling' | 'billing.estimated' | 'billing.actual' | 'billing.pricing' | 'billing.showPricing' | 'billing.hidePricing' | 'billing.pricePerM' | 'billing.input' | 'billing.output' | 'billing.cacheHit' | 'billing.peak' | 'billing.offPeak' | 'billing.flat' | 'billing.peakHours' | 'billing.band' | 'billing.openDashboard' | 'billing.close' | 'billing.footer' | 'billing.footerCredit' | 'billing.lastUpdated' | 'billing.noData' | 'billing.todayRate' | 'billing.rateLive' | 'billing.rateBuiltin' | 'billing.promoBadge' | 'billing.promoUntil' | 'billing.promoOpenEnded' | 'billing.pricingTip' | 'billing.pricingUnit' | 'billing.pricingNotes' | 'billing.ubPeak' | 'billing.ubOff' | 'billing.ubStd' | 'billing.peakBand' | 'billing.pricingSource' | 'billing.noteCache' | 'billing.noteBand' | 'billing.noteSource' | 'billing.balance' | 'billing.balanceUnconfigured' | 'billing.balanceUnauthorized' | 'billing.balanceUnreachable' | 'billing.uncatalogued' | 'billing.estimatedPricing' | 'billing.balanceDays' | 'billing.balanceLowBody' | 'billing.reconcileDrift' | 'billing.reconcileDismiss' | 'billing.subscriptions' | 'billing.subscriptionNotConfigured' | 'billing.subscriptionUnauthorized' | 'billing.subscriptionUnavailable' | 'billing.subscriptionInvalid' | 'billing.subscriptionRateLimited' | 'billing.subscriptionSession' | 'billing.subscriptionWeekly' | 'billing.subscriptionMonthly' | 'billing.subscriptionBilling' | 'billing.subscriptionRemaining' | 'billing.subscriptionExhausted' | 'billing.subscriptionReset' | 'billing.subscriptionNoApi' | 'billing.floatWindow' | 'billing.floatModeCombined' | 'billing.floatModeSubscription' | 'billing.floatMode' | 'billing.floatTargets' | 'billing.floatWindowHint' | 'billing.floatNoTargets' | 'billing.floatNoTargetsHint' | 'billing.cardDisplay' | 'billing.cardDisplayHint' | 'billing.cardMetric' | 'billing.cardMetricMoney' | 'billing.cardMetricTokens' | 'billing.triggerMonthTokens' | 'billing.floatPrev' | 'billing.floatNext' | 'billing.subscriptionsStale' | 'billing.staleLedgerNotice' | 'billing.tokenCacheWrite' | 'billing.toolRank' | 'billing.toolName' | 'billing.userPrices' | 'billing.userPricesHint' | 'billing.userPriceSave' | 'billing.userPriceModel' | 'billing.userPriceSource' | 'billing.userPriceSourceHint' | 'billing.userPriceCurrency' | 'billing.userPriceAdd' | 'billing.userPriceRemove' | 'billing.sessionStaleBadge' | 'billing.heatmapLess' | 'billing.heatmapMore' | 'billing.currency' | 'billing.currencyCny' | 'billing.currencyUsd' | 'billing.heatmap' | 'billing.rounds' | 'billing.roundsHint' | 'billing.anomaly' | 'billing.workspaces' | 'billing.workspacesHint' | 'billing.plan' | 'billing.remaining' | 'billing.unknownModel' | 'billing.model' | 'billing.thModel' | 'billing.thInputMiss' | 'billing.thInputHit' | 'billing.currentRound' | 'billing.costAbbr' | 'billing.tabOverview' | 'billing.tabTrends' | 'billing.tabProviders' | 'billing.tabDetails' | 'billing.tabPricing' | 'billing.tabSettings' | 'billing.settingsHead' | 'billing.settingsHint' | 'billing.budgetHint' | 'billing.peakAlertHint' | 'billing.peakAlertDescPeak' | 'billing.peakAlertDescOff' | 'billing.export' | 'billing.exportCsvDay' | 'billing.exportCsvSession' | 'billing.exportJson' | 'billing.peakShare' | 'billing.peakSharePerCall' | 'billing.offPeakSavings' | 'billing.perfMax' | 'billing.weekCost' | 'billing.roleCost' | 'billing.roleUser' | 'billing.roleAssistant' | 'billing.roleTool' | 'billing.roleHint' | 'billing.tierPeak' | 'billing.tierOff' | 'billing.tierToPeak' | 'billing.tierToOff' | 'billing.tierAlertEnterPeak' | 'billing.tierAlertEnterOff' | 'billing.peakAlertTitlePeak' | 'billing.peakAlertTitleOff' | 'billing.peakAlert' | 'billing.peakAlertLeadMin' | 'billing.peakAlertPos' | 'billing.peakAlertMode' | 'billing.peakAlertPosCorner' | 'billing.peakAlertPosCenter' | 'billing.peakAlertModePeak' | 'billing.peakAlertModeOff' | 'billing.peakAlertModeBoth' | 'billing.peakAlertWebNotify' | 'billing.peakAlertPreview' | 'billing.planTypeCode' | 'billing.planTypeToken' | 'billing.subscriptionFeePerMonth' | 'billing.triggerToday' | 'billing.triggerMonth' | 'billing.subscriptionIncluded' | 'billing.free' | 'billing.official' | 'billing.thirdParty' | 'billing.officialCost' | 'billing.thirdPartyCost' | 'billing.perfSamples' | 'billing.perfTtft' | 'billing.perfP50' | 'billing.perfP90' | 'billing.perfTps' | 'billing.perfLatency' | 'billing.perfEstimated' | 'billing.perfEmpty' | 'billing.perfTpsUnit' | 'billing.perfTitle' | 'billing.perfHint' | 'billing.heatmapYear' | 'billing.heatmapMonth' | 'billing.activeDays' | 'billing.streakDays' | 'billing.subscriptionAutoDetect' | 'billing.pluginInfo' | 'billing.pluginName' | 'billing.pluginDescription' | 'billing.pluginVersion' | 'billing.pluginAuthor' | 'billing.pluginRepository' | 'billing.pluginNpm' | 'billing.pluginLicense' | 'billing.tabToken' | 'billing.tokenExport' | 'billing.tokenExportCsv' | 'billing.tokenCacheHitRate' | 'billing.tokenReasoningShare' | 'billing.tokenReasoningShort' | 'billing.tokenIo' | 'billing.tokenPeak' | 'billing.tokenDaily' | 'billing.tokenByModel' | 'billing.tokenMiss' | 'billing.tokenHit' | 'billing.tokenOutput' | 'billing.tokenTotal' | 'billing.tokenShare' | 'billing.usageStatsTool' | 'billing.usageStatsToolHint' | 'billing.balanceGranted' | 'billing.balanceTopped' | 'billing.balanceDaily' | 'billing.balanceDaysLong' | 'billing.balanceDaysUnit' | 'billing.popTodayModel' | 'billing.popNoConsumption' | 'billing.popQuotaAlert' | 'billing.popRiskNone' | 'billing.popTitle' | 'billing.popDirectLead' | 'billing.popSubLead' | 'billing.popBalanceNormal' | 'billing.popBalanceLow' | 'billing.popQuotaNormal' | 'billing.popQuotaLow' | 'billing.alertBalanceLow' | 'billing.alertQuotaLow' | 'billing.unpricedHint' | 'billing.searchEstimateHint' | 'billing.siteListDisplay' | 'billing.siteListDisplayHint' | 'billing.exportCsvSite' | 'billing.panelRelay' | 'billing.relaySite' | 'billing.relayDirect' | 'billing.relayUnknown' | 'billing.panelRelayQuota' | 'billing.relayBalance' | 'billing.relayNoQuota' | 'billing.relayWindowUsed' | 'billing.relayKindNewApi' | 'billing.relayKindSub2Api' | 'billing.relayKindUnknown' | 'billing.relayCalls';
|
|
2
|
+
export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.monthCost' | 'billing.yearCost' | 'billing.monthProjected' | 'billing.liveTurn' | 'billing.liveSession' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trend7d' | 'billing.trend30d' | 'billing.trendMetric' | 'billing.trendMetricCost' | 'billing.trendMetricTokens' | 'billing.trendEmpty' | 'billing.budget' | 'billing.budgetAmount' | 'billing.budgetSummary' | 'billing.sessions' | 'billing.sessionTitle' | 'billing.project' | 'billing.lastActive' | 'billing.sessionOverflow' | 'billing.budgetTierBody' | 'billing.models' | 'billing.providerBilling' | 'billing.estimated' | 'billing.actual' | 'billing.pricing' | 'billing.showPricing' | 'billing.hidePricing' | 'billing.pricePerM' | 'billing.input' | 'billing.output' | 'billing.cacheHit' | 'billing.peak' | 'billing.offPeak' | 'billing.flat' | 'billing.peakHours' | 'billing.band' | 'billing.openDashboard' | 'billing.close' | 'billing.footer' | 'billing.footerCredit' | 'billing.lastUpdated' | 'billing.noData' | 'billing.todayRate' | 'billing.rateLive' | 'billing.rateBuiltin' | 'billing.promoBadge' | 'billing.promoUntil' | 'billing.promoOpenEnded' | 'billing.pricingTip' | 'billing.pricingUnit' | 'billing.pricingNotes' | 'billing.ubPeak' | 'billing.ubOff' | 'billing.ubStd' | 'billing.peakBand' | 'billing.pricingSource' | 'billing.noteCache' | 'billing.noteBand' | 'billing.noteSource' | 'billing.balance' | 'billing.balanceUnconfigured' | 'billing.balanceUnauthorized' | 'billing.balanceUnreachable' | 'billing.uncatalogued' | 'billing.estimatedPricing' | 'billing.balanceDays' | 'billing.balanceLowBody' | 'billing.reconcileDrift' | 'billing.reconcileDismiss' | 'billing.subscriptions' | 'billing.subscriptionNotConfigured' | 'billing.subscriptionUnauthorized' | 'billing.subscriptionUnavailable' | 'billing.subscriptionInvalid' | 'billing.subscriptionRateLimited' | 'billing.subscriptionSession' | 'billing.subscriptionWeekly' | 'billing.subscriptionMonthly' | 'billing.subscriptionBilling' | 'billing.subscriptionRemaining' | 'billing.subscriptionExhausted' | 'billing.subscriptionReset' | 'billing.subscriptionNoApi' | 'billing.floatWindow' | 'billing.floatModeCombined' | 'billing.floatModeSubscription' | 'billing.floatMode' | 'billing.floatTargets' | 'billing.floatWindowHint' | 'billing.floatNoTargets' | 'billing.floatNoTargetsHint' | 'billing.cardDisplay' | 'billing.cardDisplayHint' | 'billing.cardMetric' | 'billing.cardMetricMoney' | 'billing.cardMetricTokens' | 'billing.triggerMonthTokens' | 'billing.floatPrev' | 'billing.floatNext' | 'billing.subscriptionsStale' | 'billing.staleLedgerNotice' | 'billing.tokenCacheWrite' | 'billing.toolRank' | 'billing.toolName' | 'billing.userPrices' | 'billing.userPricesHint' | 'billing.userPriceSave' | 'billing.userPriceModel' | 'billing.userPriceSource' | 'billing.userPriceSourceHint' | 'billing.userPriceOffPeak' | 'billing.userPriceCurrency' | 'billing.userPriceAdd' | 'billing.userPriceRemove' | 'billing.sessionStaleBadge' | 'billing.heatmapLess' | 'billing.heatmapMore' | 'billing.currency' | 'billing.currencyCny' | 'billing.currencyUsd' | 'billing.heatmap' | 'billing.rounds' | 'billing.roundsHint' | 'billing.anomaly' | 'billing.workspaces' | 'billing.workspacesHint' | 'billing.plan' | 'billing.remaining' | 'billing.unknownModel' | 'billing.model' | 'billing.thModel' | 'billing.thInputMiss' | 'billing.thInputHit' | 'billing.currentRound' | 'billing.costAbbr' | 'billing.tabOverview' | 'billing.tabTrends' | 'billing.tabProviders' | 'billing.tabDetails' | 'billing.tabPricing' | 'billing.tabSettings' | 'billing.settingsHead' | 'billing.settingsHint' | 'billing.budgetHint' | 'billing.peakAlertHint' | 'billing.peakAlertDescPeak' | 'billing.peakAlertDescOff' | 'billing.export' | 'billing.exportCsvDay' | 'billing.exportCsvSession' | 'billing.exportJson' | 'billing.peakShare' | 'billing.peakSharePerCall' | 'billing.offPeakSavings' | 'billing.perfMax' | 'billing.weekCost' | 'billing.roleCost' | 'billing.roleUser' | 'billing.roleAssistant' | 'billing.roleTool' | 'billing.roleHint' | 'billing.tierPeak' | 'billing.tierOff' | 'billing.tierToPeak' | 'billing.tierToOff' | 'billing.tierAlertEnterPeak' | 'billing.tierAlertEnterOff' | 'billing.peakAlertTitlePeak' | 'billing.peakAlertTitleOff' | 'billing.peakAlert' | 'billing.peakAlertLeadMin' | 'billing.peakAlertPos' | 'billing.peakAlertMode' | 'billing.peakAlertPosCorner' | 'billing.peakAlertPosCenter' | 'billing.peakAlertModePeak' | 'billing.peakAlertModeOff' | 'billing.peakAlertModeBoth' | 'billing.peakAlertWebNotify' | 'billing.peakAlertPreview' | 'billing.planTypeCode' | 'billing.planTypeToken' | 'billing.subscriptionFeePerMonth' | 'billing.triggerToday' | 'billing.triggerMonth' | 'billing.subscriptionIncluded' | 'billing.free' | 'billing.official' | 'billing.thirdParty' | 'billing.officialCost' | 'billing.thirdPartyCost' | 'billing.perfSamples' | 'billing.perfTtft' | 'billing.perfP50' | 'billing.perfP90' | 'billing.perfTps' | 'billing.perfLatency' | 'billing.perfEstimated' | 'billing.perfEmpty' | 'billing.perfTpsUnit' | 'billing.perfTitle' | 'billing.perfHint' | 'billing.heatmapYear' | 'billing.heatmapMonth' | 'billing.activeDays' | 'billing.streakDays' | 'billing.subscriptionAutoDetect' | 'billing.pluginInfo' | 'billing.pluginName' | 'billing.pluginDescription' | 'billing.pluginVersion' | 'billing.pluginAuthor' | 'billing.pluginRepository' | 'billing.pluginNpm' | 'billing.pluginLicense' | 'billing.tabToken' | 'billing.tokenExport' | 'billing.tokenExportCsv' | 'billing.tokenCacheHitRate' | 'billing.tokenReasoningShare' | 'billing.tokenReasoningShort' | 'billing.tokenIo' | 'billing.tokenPeak' | 'billing.tokenDaily' | 'billing.tokenByModel' | 'billing.tokenMiss' | 'billing.tokenHit' | 'billing.tokenOutput' | 'billing.tokenTotal' | 'billing.tokenShare' | 'billing.usageStatsTool' | 'billing.usageStatsToolHint' | 'billing.balanceGranted' | 'billing.balanceTopped' | 'billing.balanceDaily' | 'billing.balanceDaysLong' | 'billing.balanceDaysUnit' | 'billing.popTodayModel' | 'billing.popNoConsumption' | 'billing.popQuotaAlert' | 'billing.popRiskNone' | 'billing.popTitle' | 'billing.popDirectLead' | 'billing.popSubLead' | 'billing.popBalanceNormal' | 'billing.popBalanceLow' | 'billing.popQuotaNormal' | 'billing.popQuotaLow' | 'billing.alertBalanceLow' | 'billing.alertQuotaLow' | 'billing.unpricedHint' | 'billing.searchEstimateHint' | 'billing.siteListDisplay' | 'billing.siteListDisplayHint' | 'billing.exportCsvSite' | 'billing.panelRelay' | 'billing.relaySite' | 'billing.relayDirect' | 'billing.relayUnknown' | 'billing.panelRelayQuota' | 'billing.relayBalance' | 'billing.relayNoQuota' | 'billing.relayWindowUsed' | 'billing.relayKindNewApi' | 'billing.relayKindSub2Api' | 'billing.relayKindUnknown' | 'billing.relayCalls';
|
|
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>;
|
|
@@ -41,6 +41,12 @@ export interface UserPrice {
|
|
|
41
41
|
output: number;
|
|
42
42
|
/** 计价币种;缺省 CNY。 */
|
|
43
43
|
currency?: 'CNY' | 'USD';
|
|
44
|
+
/** 低谷档三桶(元或美元 / 每百万 token);缺省 = 平档(峰谷同价)。 */
|
|
45
|
+
offPeak?: {
|
|
46
|
+
input: number;
|
|
47
|
+
cacheHit: number;
|
|
48
|
+
output: number;
|
|
49
|
+
};
|
|
44
50
|
}
|
|
45
51
|
/** 一条用户自定义价:绑定「模型(计费目录键)+ 可选来源(中转站 origin)」。
|
|
46
52
|
* origin 缺省 = 该模型的默认价;带 origin = 仅该中转站的同名模型用此价。 */
|
|
@@ -50,6 +56,22 @@ export interface UserPriceEntry extends UserPrice {
|
|
|
50
56
|
/** 绑定来源(中转站 origin,如 `https://api.my-relay.com`);缺省 = 默认价。 */
|
|
51
57
|
origin?: string;
|
|
52
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* 中转站 origin 宽松匹配:双方规范化到 `protocol://host[:port]` 后比较。
|
|
61
|
+
* 宿主侧站点桶的 origin 来自 `new URL(baseURL).origin`,用户手填的来源常缺
|
|
62
|
+
* 协议、带路径或尾斜杠——精确全等会让自定义价静默失效(issue #18)。
|
|
63
|
+
* 规范化失败(无法解析成 URL)时回退小写去尾斜杠的字面比较。
|
|
64
|
+
* @param a - 用户录入的来源(可缺协议/带路径)。
|
|
65
|
+
* @param b - 宿主站点桶的 origin(`new URL().origin` 形态)。
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* 把用户手填的中转站来源规范化为 `protocol://host[:port]` 形态:缺协议补
|
|
69
|
+
* `https://`、带路径取 origin。无法解析时回退小写去尾斜杠的字面值。
|
|
70
|
+
* 与 {@link originsMatch} 的比较口径一致——保存前规范化一次,匹配时双向兜底。
|
|
71
|
+
* @param raw - 用户录入的来源(可缺协议/带路径)。
|
|
72
|
+
*/
|
|
73
|
+
export declare function normalizeOriginInput(raw: string): string;
|
|
74
|
+
export declare function originsMatch(a: string, b: string): boolean;
|
|
53
75
|
/**
|
|
54
76
|
* 注入用户自定义单价列表。每条含模型目录键 + 可选来源(origin)。空数组 = 清除全部
|
|
55
77
|
* 自定义价,回退内置目录。
|
|
@@ -65,6 +87,20 @@ export declare function getUserPrices(): Readonly<UserPriceEntry[]> | undefined;
|
|
|
65
87
|
* @param origin - 调用来源(中转站 origin);缺省仅查默认价。
|
|
66
88
|
*/
|
|
67
89
|
export declare function userPriceOf(key: string, origin?: string): UserPrice | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* 查一个模型(可选来源)的完整用户价条目(含 origin 绑定信息)。
|
|
92
|
+
* 匹配优先级:origin 宽松精确命中(模型×来源)→ 无来源默认价。
|
|
93
|
+
* @param key - 计费目录键。
|
|
94
|
+
* @param origin - 调用来源(中转站 origin);缺省仅查默认价。
|
|
95
|
+
*/
|
|
96
|
+
export declare function userPriceEntryOf(key: string, origin?: string): UserPriceEntry | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* 查一个模型的「带来源」用户价条目(无视来源值,取第一条命中模型名的
|
|
99
|
+
* 带 origin 条目)。供 recost 在三维站点数据缺失时兜底:用户填了来源价
|
|
100
|
+
* 就按它重估,而不是静默回退宿主原价(issue #18)。
|
|
101
|
+
* @param key - 计费目录键。
|
|
102
|
+
*/
|
|
103
|
+
export declare function userOriginPriceEntryOf(key: string): UserPriceEntry | undefined;
|
|
68
104
|
/**
|
|
69
105
|
* Apply the node half's live pricing snapshot. Absent fields keep the
|
|
70
106
|
* built-in catalog and rate; callers never fabricate values.
|
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.0.
|
|
4
|
+
"version": "1.0.13",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|