dsh-cost-meter 1.7.47 → 1.7.49
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.md +6 -6
- package/README.zh-CN.md +6 -6
- package/lib/backfill.js +9 -3
- package/lib/client.js +5 -5
- package/lib/index.js +4 -0
- package/lib/native-search-billing.js +28 -2
- package/lib/store.js +5 -1
- package/lib/turn-cost.js +71 -0
- package/lib/typert.host.js +21 -0
- package/package.json +3 -2
package/lib/index.js
CHANGED
|
@@ -37,6 +37,7 @@ import { readExternalUsageSnapshot, externalTodayCostForReconcile } from './exte
|
|
|
37
37
|
import { queryQwenCli } from './qwen-cli.js'
|
|
38
38
|
import { queryBailianCli } from './bailian-cli.js'
|
|
39
39
|
import { getSessionCost as readSessionCost } from './session-tree.js'
|
|
40
|
+
import { getTurnCost } from './turn-cost.js'
|
|
40
41
|
|
|
41
42
|
export const name = 'cost-meter'
|
|
42
43
|
|
|
@@ -2391,6 +2392,9 @@ function createService(ctx, ledger, openRouterPrices) {
|
|
|
2391
2392
|
async getSessionCost(sessionId) {
|
|
2392
2393
|
return readSessionCost(ledger, ctx, sessionId)
|
|
2393
2394
|
},
|
|
2395
|
+
async getTurnCost(sessionId, startSeq, endSeq) {
|
|
2396
|
+
return getTurnCost(ledger, ctx, sessionId, startSeq, endSeq)
|
|
2397
|
+
},
|
|
2394
2398
|
async getTopSessions(limit, sort = 'cost', dir = 'desc') {
|
|
2395
2399
|
const n = Math.max(1, Math.min(500, Math.floor(Number(limit)) || 100))
|
|
2396
2400
|
const sortKey = sort === 'time' || sort === 'recent' ? sort : 'cost'
|
|
@@ -72,6 +72,7 @@ function parseBody(request) {
|
|
|
72
72
|
*/
|
|
73
73
|
export function createNativeSearchBilling({ account, record = () => {}, uncovered = () => {}, now = Date.now, targetRequest = officialRequest, diagnostics = channel }) {
|
|
74
74
|
const scope = new AsyncLocalStorage()
|
|
75
|
+
const searchSignal = new AsyncLocalStorage()
|
|
75
76
|
const requests = new WeakMap()
|
|
76
77
|
const pending = new Set()
|
|
77
78
|
const subscriptions = []
|
|
@@ -98,6 +99,7 @@ export function createNativeSearchBilling({ account, record = () => {}, uncovere
|
|
|
98
99
|
item.chunks.push(Buffer.from(chunk))
|
|
99
100
|
}
|
|
100
101
|
const release = (request, item) => {
|
|
102
|
+
clearTimeout(item.timer)
|
|
101
103
|
item.chunks = []
|
|
102
104
|
try { item.restore?.() } catch {}
|
|
103
105
|
item.restore = undefined
|
|
@@ -106,13 +108,23 @@ export function createNativeSearchBilling({ account, record = () => {}, uncovere
|
|
|
106
108
|
requests.delete(request)
|
|
107
109
|
}
|
|
108
110
|
subscribe('create', ({ request }) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
if (!targetRequest(request)) return
|
|
112
|
+
const signal = searchSignal.getStore()
|
|
113
|
+
const scoped = scope.getStore()
|
|
114
|
+
const current = scoped ?? (signal?.pending && now() - signal.at < 60_000 ? signal : null)
|
|
115
|
+
if (!current) return
|
|
116
|
+
if (signal) signal.pending = false
|
|
111
117
|
const item = { session: current.session, startedAtMs: now(), requestId: randomUUID(), bytes: 0, chunks: [], encoding: '', status: 0, done: false }
|
|
112
118
|
requests.set(request, item)
|
|
113
119
|
current.requests.push(item)
|
|
114
120
|
pending.add(item)
|
|
115
121
|
item.release = () => release(request, item)
|
|
122
|
+
if (!scoped) {
|
|
123
|
+
item.timer = setTimeout(() => {
|
|
124
|
+
item.done = true; miss(item, 'response-incomplete'); release(request, item)
|
|
125
|
+
}, 60_000)
|
|
126
|
+
item.timer.unref?.()
|
|
127
|
+
}
|
|
116
128
|
// Node 20/22 的 undici 6 尚无响应 body 诊断通道。只观察这一请求的
|
|
117
129
|
// onData,不更换 dispatcher/fetch;保留 this/返回值,并在结束/卸载时恢复。
|
|
118
130
|
// 这是有能力检测的旧运行时兼容层;宿主更换网络实现后退回缺口提示。
|
|
@@ -169,6 +181,18 @@ export function createNativeSearchBilling({ account, record = () => {}, uncovere
|
|
|
169
181
|
release(request, item)
|
|
170
182
|
})
|
|
171
183
|
return {
|
|
184
|
+
// The host publishes this exact event immediately before native search
|
|
185
|
+
// dispatch. It gives the request its own session boundary even when another
|
|
186
|
+
// plugin replaces web.search or the tool invokes a previously saved method.
|
|
187
|
+
signal(session, event) {
|
|
188
|
+
if (!active) return
|
|
189
|
+
if (event?.type === 'web/deepseek-search-llm-request') {
|
|
190
|
+
searchSignal.enterWith({ session, at: now(), pending: event.data?.endpoint === 'https://api.deepseek.com/anthropic/v1/messages', requests: [] })
|
|
191
|
+
} else if (['tool/result', 'turn/end', 'step/end'].includes(event?.type)) {
|
|
192
|
+
const current = searchSignal.getStore()
|
|
193
|
+
if (current?.session?.id === session?.id) current.pending = false
|
|
194
|
+
}
|
|
195
|
+
},
|
|
172
196
|
async run(operation, session) {
|
|
173
197
|
if (!active || scope.getStore()) return operation()
|
|
174
198
|
const current = { session, requests: [] }
|
|
@@ -189,6 +213,7 @@ export function createNativeSearchBilling({ account, record = () => {}, uncovere
|
|
|
189
213
|
for (const item of pending) { item.done = true; item.release() }
|
|
190
214
|
pending.clear()
|
|
191
215
|
scope.disable()
|
|
216
|
+
searchSignal.disable()
|
|
192
217
|
},
|
|
193
218
|
}
|
|
194
219
|
}
|
|
@@ -252,6 +277,7 @@ export function installNativeSearchBilling(ctx, ledger) {
|
|
|
252
277
|
uncovered: (event) => coverage.add(event),
|
|
253
278
|
})
|
|
254
279
|
monitors.set(ledger, { coverage })
|
|
280
|
+
ctx.on?.('session/event', (session, event) => monitor.signal(session, event), { global: true })
|
|
255
281
|
const observations = new Map()
|
|
256
282
|
const observe = (service, webCtx = ctx) => {
|
|
257
283
|
if (!service) return
|
package/lib/store.js
CHANGED
|
@@ -143,6 +143,7 @@ export function defaultConfig() {
|
|
|
143
143
|
showTotalWithPlan: false, // 「含 Plan 总额」全局开关(v1.6.0):开启后全部金额展示按总等值(cost)计;默认按真金白银(apiCost)
|
|
144
144
|
includeSubagentCost: false, // 本会话展示可合并连续子代理后代;不重复写入每日账本。
|
|
145
145
|
codexQuotaEnabled: false, // Codex 额度按需开启,避免未使用 Codex 时显示卡片或查询。
|
|
146
|
+
codexQuotaSidebar: true, // 独立控制侧栏卡片,保留会话额度条。
|
|
146
147
|
sidebarSimple: false,
|
|
147
148
|
sidebarSimplePromptSeen: false,
|
|
148
149
|
sidebarModels: { enabled: false, period: 'today', topN: 5, summary: 'total', position: 'last', defaultOpen: false, remember: true, tokens: false, shares: true, refreshSeconds: 60, dock: false },
|
|
@@ -740,6 +741,7 @@ const VALIDATION_MESSAGES = {
|
|
|
740
741
|
showTotalWithPlan: 'showTotalWithPlan 必须是布尔值',
|
|
741
742
|
includeSubagentCost: 'includeSubagentCost 必须是布尔值',
|
|
742
743
|
codexQuotaEnabled: 'codexQuotaEnabled 必须是布尔值',
|
|
744
|
+
codexQuotaSidebar: 'codexQuotaSidebar 必须是布尔值',
|
|
743
745
|
sidebarSimple: '简化显示设置必须是布尔值',
|
|
744
746
|
sidebarModels: '模型花费卡片设置无效:Top-N 为 1–10,刷新间隔为 10–60 秒',
|
|
745
747
|
sidebarStyle: 'sidebarStyle 必须是 standard / compact',
|
|
@@ -844,6 +846,7 @@ const VALIDATION_MESSAGES = {
|
|
|
844
846
|
showTotalWithPlan: 'showTotalWithPlan must be a boolean',
|
|
845
847
|
includeSubagentCost: 'includeSubagentCost must be a boolean',
|
|
846
848
|
codexQuotaEnabled: 'codexQuotaEnabled must be a boolean',
|
|
849
|
+
codexQuotaSidebar: 'codexQuotaSidebar must be a boolean',
|
|
847
850
|
sidebarSimple: 'Simple display settings must be boolean',
|
|
848
851
|
sidebarModels: 'Invalid model cost card settings: Top-N must be 1–10 and refresh interval 10–60 seconds',
|
|
849
852
|
sidebarStyle: 'sidebarStyle must be standard / compact',
|
|
@@ -1142,7 +1145,7 @@ export function applyConfigPatch(current, rawPatch) {
|
|
|
1142
1145
|
if (candidate.sidebarTodayMetric !== undefined && !['cost', 'tokens'].includes(candidate.sidebarTodayMetric)) errors.push('sidebarTodayMetric must be cost or tokens')
|
|
1143
1146
|
// 「含 Plan 总额」全局开关(v1.6.0):布尔,可缺省(默认关)。
|
|
1144
1147
|
if (candidate.showTotalWithPlan !== undefined && typeof candidate.showTotalWithPlan !== 'boolean') errors.push(vmsg(locale, 'showTotalWithPlan'))
|
|
1145
|
-
for (const field of ['includeSubagentCost', 'codexQuotaEnabled']) {
|
|
1148
|
+
for (const field of ['includeSubagentCost', 'codexQuotaEnabled', 'codexQuotaSidebar']) {
|
|
1146
1149
|
if (candidate[field] !== undefined && typeof candidate[field] !== 'boolean') errors.push(vmsg(locale, field))
|
|
1147
1150
|
}
|
|
1148
1151
|
// 侧边栏进度条样式:standard(默认,纵向单列) | compact(额度卡两列网格)。
|
|
@@ -1480,6 +1483,7 @@ export function sanitizeConfig(raw) {
|
|
|
1480
1483
|
out.showTotalWithPlan = out.showTotalWithPlan === true
|
|
1481
1484
|
out.includeSubagentCost = out.includeSubagentCost === true
|
|
1482
1485
|
out.codexQuotaEnabled = out.codexQuotaEnabled === true
|
|
1486
|
+
out.codexQuotaSidebar = out.codexQuotaSidebar !== false
|
|
1483
1487
|
out.sidebarSimple = out.sidebarSimple === true
|
|
1484
1488
|
out.sidebarSimplePromptSeen = out.sidebarSimplePromptSeen === true
|
|
1485
1489
|
out.sidebarModels = Object.fromEntries(Object.entries(base.sidebarModels).map(([k, fallback]) => {
|
package/lib/turn-cost.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { statSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
|
|
4
|
+
import { listSessionLogs, iterateSessionRecords, replaySessionRecords } from './backfill.js'
|
|
5
|
+
import { nativeSearchRecordsFor } from './native-search-history.js'
|
|
6
|
+
import { tierFor, usdFromCost } from './pricing.js'
|
|
7
|
+
|
|
8
|
+
const cache = new Map()
|
|
9
|
+
const fields = { input: 'cacheMiss', output: 'output', cacheRead: 'cacheHit', cacheWrite: 'cacheWrite', reasoning: 'reasoning' }
|
|
10
|
+
const emptyTurn = () => ({ found: false, cost: 0, apiCost: 0, rows: [], calls: [] })
|
|
11
|
+
export function turnCostFromRecords(records, config, startSeq, endSeq, native = []) {
|
|
12
|
+
const start = records.find(row => row.seq === startSeq && row.type === 'turn/start')
|
|
13
|
+
const end = records.find(row => row.seq === endSeq && row.type === 'turn/end')
|
|
14
|
+
if (!start || !end || endSeq < startSeq || start.data?.turn !== end.data?.turn) return emptyTurn()
|
|
15
|
+
const headers = records.filter(row => row.type === 'request/header' && row.seq < startSeq).slice(-1)
|
|
16
|
+
const selected = [...headers, ...records.filter(row => row.seq >= startSeq && row.seq <= endSeq),
|
|
17
|
+
...native.filter(row => row.data.startedAtMs >= start.time && row.data.startedAtMs <= end.time)]
|
|
18
|
+
const { samples } = replaySessionRecords(selected, config, null, true)
|
|
19
|
+
const parts = new Map()
|
|
20
|
+
const calls = []
|
|
21
|
+
for (const sample of samples) {
|
|
22
|
+
let tier = tierFor(sample.resolved.entry, sample.atMs, sample.peak)
|
|
23
|
+
const longContext = !!tier.longContext && sample.buckets.input + sample.buckets.cacheRead + sample.buckets.cacheWrite > tier.longContext.aboveInputTokens
|
|
24
|
+
if (longContext) tier = tier.longContext
|
|
25
|
+
const call = { kind: sample.kind, provider: sample.provider, model: sample.model, atMs: sample.atMs, cost: sample.cost, apiCost: sample.apiCost, plan: sample.plan, priced: sample.resolved.priced, longContext, rows: [] }
|
|
26
|
+
for (const [bucket, priceKey] of Object.entries(fields)) {
|
|
27
|
+
const tokens = sample.buckets[bucket]
|
|
28
|
+
if (!tokens) continue
|
|
29
|
+
const rate = sample.resolved.priced ? usdFromCost(tier[priceKey] ?? (bucket === 'cacheWrite' ? tier.cacheHit : 0), sample.resolved.currency, config.exchangeRate) : 0
|
|
30
|
+
const plan = sample.plan
|
|
31
|
+
call.rows.push({ provider: sample.provider, model: sample.model, bucket, tokens, rate, cost: tokens * rate / 1e6, priced: sample.resolved.priced, plan })
|
|
32
|
+
const key = JSON.stringify([sample.provider, sample.model, bucket, rate, plan, sample.resolved.priced])
|
|
33
|
+
const row = parts.get(key) ?? { provider: sample.provider, model: sample.model, bucket, tokens: 0, rate, cost: 0, priced: sample.resolved.priced, plan }
|
|
34
|
+
row.tokens += tokens; row.cost += tokens * rate / 1e6
|
|
35
|
+
parts.set(key, row)
|
|
36
|
+
}
|
|
37
|
+
calls.push(call)
|
|
38
|
+
}
|
|
39
|
+
calls.sort((a, b) => a.atMs - b.atMs)
|
|
40
|
+
return { found: samples.length > 0, cost: samples.reduce((n, row) => n + row.cost, 0), apiCost: samples.reduce((n, row) => n + row.apiCost, 0), rows: [...parts.values()], calls }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function getTurnCost(ledger, ctx, sessionId, startSeq, endSeq) {
|
|
44
|
+
if (typeof sessionId !== 'string' || !sessionId || sessionId.length > 512 || ![startSeq, endSeq].every(n => Number.isSafeInteger(n) && n >= 0) || endSeq < startSeq) throw new Error('invalid turn identity')
|
|
45
|
+
const live = ctx?.get?.('sessions')?.get?.(sessionId)?.snapshotEvents?.()
|
|
46
|
+
if (Array.isArray(live)) return turnCostFromRecords(live, ledger.config, startSeq, endSeq, await nativeSearchRecordsFor(ledger.path, sessionId))
|
|
47
|
+
const paths = listSessionLogs(join(resolveDshHome(), 'sessions'), new Set([sessionId]))
|
|
48
|
+
if (!paths.length) return emptyTurn()
|
|
49
|
+
const path = paths[0], info = statSync(path)
|
|
50
|
+
const key = `${path}:${info.size}:${info.mtimeMs}`
|
|
51
|
+
let pending = cache.get(key)
|
|
52
|
+
if (!pending) {
|
|
53
|
+
pending = (async () => {
|
|
54
|
+
const rows = []
|
|
55
|
+
for await (const row of iterateSessionRecords(path)) {
|
|
56
|
+
if (row.type === 'request/header') rows.push({ type: row.type, seq: row.seq, time: row.time, data: { header: { config: { model: row.data?.header?.config?.model, provider: row.data?.header?.config?.provider } } } })
|
|
57
|
+
else if (['turn/start', 'turn/end', 'compaction/summary', 'assistant/message'].includes(row.type) || row.type === 'assistant/chunk' && row.data?.chunk?.type === 'usage') {
|
|
58
|
+
rows.push({ type: row.type, seq: row.seq, time: row.time, data: { turn: row.data?.turn, step: row.data?.step, provider: row.data?.provider, model: row.data?.model,
|
|
59
|
+
usage: row.data?.usage, message: { source: row.data?.message?.source }, chunk: row.data?.chunk?.type === 'usage' ? row.data.chunk : undefined } })
|
|
60
|
+
}
|
|
61
|
+
if (rows.length > 200_000) throw new Error('Session usage history exceeds the turn detail limit')
|
|
62
|
+
}
|
|
63
|
+
return rows
|
|
64
|
+
})()
|
|
65
|
+
cache.set(key, pending)
|
|
66
|
+
if (cache.size > 16) cache.delete(cache.keys().next().value)
|
|
67
|
+
pending.catch(() => cache.delete(key))
|
|
68
|
+
}
|
|
69
|
+
const records = await pending
|
|
70
|
+
return turnCostFromRecords(records, ledger.config, startSeq, endSeq, await nativeSearchRecordsFor(ledger.path, sessionId))
|
|
71
|
+
}
|
package/lib/typert.host.js
CHANGED
|
@@ -59,6 +59,12 @@ export const sessionCostSchema = z.object({
|
|
|
59
59
|
subagentCount: num,
|
|
60
60
|
found: z.boolean(),
|
|
61
61
|
})
|
|
62
|
+
const turnCostRowSchema = z.object({ provider: z.string(), model: z.string(), bucket: z.string(), tokens: num, rate: num, cost: num, priced: z.boolean(), plan: z.boolean() })
|
|
63
|
+
export const turnCostSchema = z.object({
|
|
64
|
+
found: z.boolean(), cost: num, apiCost: num,
|
|
65
|
+
rows: z.array(turnCostRowSchema),
|
|
66
|
+
calls: z.array(z.object({ kind: z.string(), provider: z.string(), model: z.string(), atMs: num, cost: num, apiCost: num, plan: z.boolean(), priced: z.boolean(), longContext: z.boolean(), rows: z.array(turnCostRowSchema) })),
|
|
67
|
+
})
|
|
62
68
|
|
|
63
69
|
const longContextSchema = z.object({
|
|
64
70
|
aboveInputTokens: z.number().positive(),
|
|
@@ -160,6 +166,7 @@ const configSchema = z.object({
|
|
|
160
166
|
showTotalWithPlan: z.boolean().optional(),
|
|
161
167
|
includeSubagentCost: z.boolean().optional(),
|
|
162
168
|
codexQuotaEnabled: z.boolean().optional(),
|
|
169
|
+
codexQuotaSidebar: z.boolean().optional(),
|
|
163
170
|
sidebarSimple: z.boolean().optional(),
|
|
164
171
|
sidebarSimplePromptSeen: z.boolean().optional(),
|
|
165
172
|
sidebarModels: z.object({ enabled: z.boolean(), period: z.enum(['today', 'history']), topN: z.number().int().min(1).max(10), summary: z.enum(['total', 'top']), position: z.enum(['first', 'afterBalance', 'last']), defaultOpen: z.boolean(), remember: z.boolean(), tokens: z.boolean(), shares: z.boolean(), refreshSeconds: z.number().int().min(10).max(60), dock: z.boolean() }).strict().optional(),
|
|
@@ -523,6 +530,8 @@ const _sort$codec = strictCodec('SessionSort', z.string())
|
|
|
523
530
|
const _dir$codec = strictCodec('SessionSortDir', z.string())
|
|
524
531
|
const _sessionId$codec = strictCodec('SessionId', z.string().min(1).max(512))
|
|
525
532
|
const _sessionCost$codec = strictCodec('SessionCost', sessionCostSchema)
|
|
533
|
+
const _turnCost$codec = strictCodec('TurnCost', turnCostSchema)
|
|
534
|
+
const _turnSeq$codec = strictCodec('TurnSeq', z.number().int().nonnegative())
|
|
526
535
|
|
|
527
536
|
export const TYPERT = {
|
|
528
537
|
package: 'dsh-cost-meter',
|
|
@@ -673,6 +682,12 @@ export const TYPERT = {
|
|
|
673
682
|
parameters: [{ name: 'sessionId', wire: 'sessionId', source: 'json', codec: _sessionId$codec }],
|
|
674
683
|
result: _sessionCost$codec,
|
|
675
684
|
},
|
|
685
|
+
{
|
|
686
|
+
id: 'dsh-cost-meter#costMeter/getTurnCost', service: 'costMeter', namespace: 'costMeter', method: 'getTurnCost',
|
|
687
|
+
invocation: { kind: 'direct' },
|
|
688
|
+
parameters: [{ name: 'sessionId', wire: 'sessionId', source: 'json', codec: _sessionId$codec }, { name: 'startSeq', wire: 'startSeq', source: 'json', codec: _turnSeq$codec }, { name: 'endSeq', wire: 'endSeq', source: 'json', codec: _turnSeq$codec }],
|
|
689
|
+
result: _turnCost$codec,
|
|
690
|
+
},
|
|
676
691
|
{
|
|
677
692
|
// 写入一枚密钥到 DSH 凭据库(v1.6.8):密钥不再经 updateConfig 传递,值只进凭据库。
|
|
678
693
|
id: 'dsh-cost-meter#costMeter/setCredential',
|
|
@@ -786,6 +801,12 @@ export const TYPERT = {
|
|
|
786
801
|
summary: '跨全部日期按指定排序返回前 N 个会话(不分日期视角)。Return the top N sessions across all days with the given sort.',
|
|
787
802
|
jsDoc: '/**\n * 跨全部日期返回前 N 个会话(每条带所属日期/标题/创建时刻)。\n * @param limit - 返回条数上限(服务端限制 1-500)。\n * @param sort - cost(费用) | time(创建时间) | recent(实时顺序)。\n * @param dir - asc | desc(默认 desc)。\n * @returns 会话列表(含 date/title/at 字段)。\n * Return the top N sessions across all days (each tagged with date/title/createdAt).\n * @param limit - Max rows to return (server clamps to 1-500).\n * @param sort - cost | time | recent (ledger/sidebar order).\n * @param dir - asc | desc (defaults to desc).\n * @returns Session list with date/title/at fields.\n */',
|
|
788
803
|
},
|
|
804
|
+
{
|
|
805
|
+
kind: 'method',
|
|
806
|
+
name: 'getTurnCost',
|
|
807
|
+
signature: 'getTurnCost(sessionId: string, startSeq: number, endSeq: number): TurnCost',
|
|
808
|
+
summary: '读取已完成轮次的分项用量与估算费用。Read recorded usage and estimated costs for a completed turn.',
|
|
809
|
+
},
|
|
789
810
|
{
|
|
790
811
|
kind: 'method',
|
|
791
812
|
name: 'getSessionCost',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cost-meter",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.49",
|
|
4
4
|
"description": "DeepSeek Harness 会话费用统计插件:本会话成本、当日费用、历史记录与官方价格同步,支持多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配)、主流 Coding Plan 订阅额度查询与显示(11 家,含小米 MiMo Token Plan 控制台 Cookie 查询、SCNet Token Plan 本地 Credits 计量与火山方舟 Volcano Ark Coding Plan AK/SK 签名查询)、自定义 Provider 余额查询(可配任意 HTTP 端点)与余额进度条、峰谷计价时段显示与切换前弹窗/系统通知提醒(位置/提前量/类型可配),界面中英双语。Session cost tracking plugin for DeepSeek Harness: per-conversation cost, daily totals, history and official price sync, with multi-vendor model pricing (built-in 90+ model catalog with auto-matching), Coding Plan quota queries & display for 11 vendors (incl. Xiaomi MiMo Token Plan via console cookie, local credits metering for the SCNet Token Plan and Volcano Ark Coding Plan via AK/SK signature), plan/API dual-track billing with per-1% & full-window Token Plan usage estimates, custom provider balance lookup (configurable HTTP endpoint) and balance progress bar, plus peak/off-peak pricing display with pre-switch popup & system-notification alerts (position / lead time / type configurable), in a bilingual (Chinese/English) UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"0.1.3-alpha.1": "unknown",
|
|
38
38
|
"0.1.3-alpha.2": "compatible",
|
|
39
39
|
"0.1.5-alpha.1": "compatible",
|
|
40
|
-
"0.2.0-rc.1": "compatible"
|
|
40
|
+
"0.2.0-rc.1": "compatible",
|
|
41
|
+
"0.2.0-rc.2": "compatible"
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
},
|