dsh-api-dashboard 1.4.2 → 1.4.3

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 CHANGED
@@ -125,7 +125,7 @@ dsh plugin --profile web add link:/root/dsha-api-dashboard
125
125
 
126
126
  ## 更新日志
127
127
 
128
- 最近一版 **v1.4.1** —— 安全与稳定性审计修复。一版一行的完整历史见 [`CHANGELOG.md`](CHANGELOG.md)。
128
+ 最近一版 **v1.4.3** —— 子代理胶囊只显示**它自己新产生**的消耗(不再把父会话继承来的历史、兄弟或后代子代理的消耗算进去)。一版一行的完整历史见 [`CHANGELOG.md`](CHANGELOG.md)。
129
129
 
130
130
  ## License
131
131
 
package/client/client.js CHANGED
@@ -858,13 +858,15 @@ window.__ModuleLoader__.load({
858
858
  ref: (n) => { if (n) requestAnimationFrame(() => syncSubsOverflow(n)); },
859
859
  onScroll: (e) => syncSubsOverflow(e.currentTarget) },
860
860
  list.map((s, i) => {
861
- const f = formatSessionCost({ cost: s.cost, currency: mainCur, costByCurrency: s.costByCurrency, waiting: false });
861
+ const f = formatSessionCost({ cost: s.cost, currency: mainCur, costByCurrency: s.costByCurrency, waiting: s.waiting === true });
862
+ // waiting(等自身数据) 时 formatSessionCost 直接返回 null → "~—";
863
+ // 真实零消耗时 f 非 null 但 hasValue=false, 也必须回落 "~—"(与 v1.4.x 一致), 不能显示 ~¥0.00
862
864
  const amt = f !== null && f.hasValue ? "~" + f.text : "~—";
863
865
  const kind = s.mode === "continuable" ? "可续聊子代理" : "一次性子代理";
864
866
  const models = (s.models || []).join(", ");
865
867
  const tip = [
866
868
  kind + " " + (s.label || "子代理"),
867
- "消耗 " + (f !== null ? f.title : ""),
869
+ "自身消耗 " + (f !== null ? f.title : "等待自身用量数据"),
868
870
  models ? "模型 " + models : "",
869
871
  s.id ? "ID " + String(s.id).slice(0, 8) : "",
870
872
  ].filter(Boolean).join("\n");
@@ -2645,7 +2647,7 @@ window.__ModuleLoader__.load({
2645
2647
 
2646
2648
  // v1.4.0: 子代理消耗行 —— 有子代理时在状态条**下面换行**追加一行,
2647
2649
  // 按父会话里的创建顺序从左到右排列 (顺序由服务端 subagentCatalog 决定, 这里不排序)。
2648
- // 金额是「该子代理及其后代」的汇总, 与主板数字同一套计价口径。
2650
+ // 只计该子代理自身新用量,不含继承历史、兄弟或后代。
2649
2651
  const subRow = buildSubagentRow(cost);
2650
2652
 
2651
2653
  return react.createElement("span", { className: "dshadb_barwrap" }, [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-api-dashboard",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "description": "DeepSeek Harness 多平台 API 余额 / 用量看板:输入框下方实时显示各平台余额与本会话估算消耗",
5
5
  "keywords": [
6
6
  "dsh",
package/src/index.js CHANGED
@@ -1726,7 +1726,8 @@ const readSessionCacheRecord = (sessionId) => {
1726
1726
 
1727
1727
  /** 冷路径: 从缓存文件里取该会话的 queryBalanceCost **状态**(不是 wire 视图)。 */
1728
1728
  const cachedCostState = (sessionId) => {
1729
- const val = readSessionCacheRecord(sessionId)?.queryBalanceCost?.val
1729
+ const row = readSessionCacheRecord(sessionId)?.queryBalanceCost
1730
+ const val = row?.ver === 3 ? row.val : null
1730
1731
  return (val !== null && typeof val === 'object' && Array.isArray(val.modelOrder) && val.byModel !== null && typeof val.byModel === 'object') ? val : null
1731
1732
  }
1732
1733
 
@@ -1745,7 +1746,7 @@ const cachedCatalog = (sessionId) => {
1745
1746
  }))
1746
1747
  }
1747
1748
 
1748
- export function collectSubagentCosts(services, rootSessionId, summarize) {
1749
+ export function collectSubagentCosts(services, rootSessionId, summarize, ownChildIds) {
1749
1750
  const out = []
1750
1751
  if (typeof rootSessionId !== 'string' || rootSessionId === '') return out
1751
1752
  let sessions = null, projections = null
@@ -1779,32 +1780,26 @@ export function collectSubagentCosts(services, rootSessionId, summarize) {
1779
1780
  if (s !== null && projections !== null) {
1780
1781
  try {
1781
1782
  const st = projections.stateOf(s, 'queryBalanceCost')
1782
- if (st !== null && st !== undefined && Array.isArray(st.modelOrder) && st.modelOrder.length > 0) return st
1783
+ if (st !== null && st !== undefined) return st
1783
1784
  } catch { /* 落到冷路径 */ }
1784
1785
  }
1785
1786
  return cachedCostState(sessionId)
1786
1787
  }
1787
1788
 
1788
- /** 递归汇总: 自身 + 后代, 返回与 summarize 同形的汇总。 */
1789
- const rollup = (sessionId, depth) => {
1790
- const st = costStateOf(sessionId)
1791
- let acc = (st !== null && subagentCostSummarize !== null) ? subagentCostSummarize(st) : null
1792
- if (depth >= SUBAGENT_MAX_DEPTH) return acc
1793
- for (const entry of childrenOf(sessionId)) {
1794
- acc = mergeSummary(acc, rollup(entry.id, depth + 1))
1795
- }
1796
- return acc
1797
- }
1798
-
1799
1789
  for (const entry of childrenOf(rootSessionId)) {
1800
1790
  if (out.length >= SUBAGENT_MAX_ROWS) break
1801
- const s = rollup(entry.id, 1) ?? emptySummary()
1791
+ if (Array.isArray(ownChildIds) && !ownChildIds.includes(entry.id)) continue
1792
+ const st = costStateOf(entry.id)
1793
+ const ready = st?.ownBoundaryKnown === true && st?.own && Array.isArray(st.own.modelOrder)
1794
+ const waiting = !ready || st.own.modelOrder.length === 0
1795
+ const s = ready ? summarize(st.own) : emptySummary()
1802
1796
  out.push({
1803
1797
  id: String(entry.id),
1804
1798
  label: typeof entry.label === 'string' && entry.label !== '' ? entry.label : String(entry.id).slice(0, 12),
1805
1799
  mode: entry.mode === 'continuable' ? 'continuable' : 'one-shot',
1806
1800
  createdAt: typeof entry.createdAt === 'number' ? entry.createdAt : 0,
1807
- cost: s.cost,
1801
+ cost: waiting ? -1 : s.cost,
1802
+ waiting,
1808
1803
  costByCurrency: s.costByCurrency,
1809
1804
  currencyByModel: s.currencyByModel,
1810
1805
  mixedCurrency: s.mixedCurrency,
@@ -1952,39 +1947,7 @@ export function makeCostProjection(configOrGetter, services) {
1952
1947
  }
1953
1948
  }
1954
1949
 
1955
- return {
1956
- key: 'queryBalanceCost',
1957
- // 框架要求的投影定义 API: stateSchema(内部状态) + wire.{viewSchema,view}(客户端可见视图)。
1958
- // 旧版误用顶层 schema+view, 导致 wire 缺失, 服务端 drive 永不通知、客户端永远拿不到值。
1959
- stateSchema: z.object({
1960
- currentModel: z.string().nullable(),
1961
- currentProvider: z.string().nullable(),
1962
- last: z.object({
1963
- turn: z.number(),
1964
- step: z.number(),
1965
- model: z.string(),
1966
- buckets: z.object({
1967
- uncachedInputTokens: z.number(),
1968
- cacheReadTokens: z.number(),
1969
- cacheWriteTokens: z.number(),
1970
- outputTokens: z.number(),
1971
- }),
1972
- }).nullable(),
1973
- byModel: z.record(z.string(), z.object({
1974
- uncachedInputTokens: z.number(),
1975
- cacheReadTokens: z.number(),
1976
- cacheWriteTokens: z.number(),
1977
- outputTokens: z.number(),
1978
- })),
1979
- modelOrder: z.array(z.string()),
1980
- /** v1.4.0: 本投影所属会话 id —— 子代理汇总要拿它去查 `subagentCatalog`。空串表示未知。 */
1981
- sessionId: z.string(),
1982
- }),
1983
- init: (header) => ({
1984
- currentModel: null, currentProvider: null, last: null, byModel: {}, modelOrder: [],
1985
- sessionId: typeof header?.id === 'string' ? header.id : '',
1986
- }),
1987
- apply: (state, event) => {
1950
+ const foldUsage = (state, event) => {
1988
1951
  // v1.4.0: `llm/retry-started` 关闭「替换槽位」—— 被重试的那次 attempt 的用量要**留在总量里**,
1989
1952
  // 下一次 attempt 是**新增**而不是替换。与 dsh-token-meter 的 usage-projection 对齐。
1990
1953
  if (event.type === 'llm/retry-started') {
@@ -2027,6 +1990,56 @@ export function makeCostProjection(configOrGetter, services) {
2027
1990
  if (prev !== null) byModel = { ...byModel, [prev.model]: subBuckets(byModel[prev.model] ?? zero(), prev.buckets) }
2028
1991
  byModel = { ...byModel, [model]: addBuckets(byModel[model] ?? zero(), buckets) }
2029
1992
  return { ...state, currentModel: nextModel, currentProvider: nextProvider, last: { turn, step, model, buckets }, byModel, modelOrder: isNewModel ? [...state.modelOrder, model] : state.modelOrder }
1993
+ }
1994
+
1995
+ return {
1996
+ key: 'queryBalanceCost',
1997
+ // 框架要求的投影定义 API: stateSchema(内部状态) + wire.{viewSchema,view}(客户端可见视图)。
1998
+ // 旧版误用顶层 schema+view, 导致 wire 缺失, 服务端 drive 永不通知、客户端永远拿不到值。
1999
+ stateSchema: z.object({
2000
+ currentModel: z.string().nullable(),
2001
+ currentProvider: z.string().nullable(),
2002
+ last: z.object({
2003
+ turn: z.number(),
2004
+ step: z.number(),
2005
+ model: z.string(),
2006
+ buckets: z.object({
2007
+ uncachedInputTokens: z.number(),
2008
+ cacheReadTokens: z.number(),
2009
+ cacheWriteTokens: z.number(),
2010
+ outputTokens: z.number(),
2011
+ }),
2012
+ }).nullable(),
2013
+ byModel: z.record(z.string(), z.object({
2014
+ uncachedInputTokens: z.number(),
2015
+ cacheReadTokens: z.number(),
2016
+ cacheWriteTokens: z.number(),
2017
+ outputTokens: z.number(),
2018
+ })),
2019
+ modelOrder: z.array(z.string()),
2020
+ /** v1.4.0: 本投影所属会话 id —— 子代理汇总要拿它去查 `subagentCatalog`。空串表示未知。 */
2021
+ sessionId: z.string(),
2022
+ inheritedEventCount: z.number().int().nonnegative(),
2023
+ ownBoundaryKnown: z.boolean(),
2024
+ ownChildIds: z.array(z.string()),
2025
+ own: z.object({ currentModel: z.string().nullable(), currentProvider: z.string().nullable(), last: z.any().nullable(), byModel: z.record(z.string(), z.object({ uncachedInputTokens: z.number().nonnegative(), cacheReadTokens: z.number().nonnegative(), cacheWriteTokens: z.number().nonnegative(), outputTokens: z.number().nonnegative() })), modelOrder: z.array(z.string()) }),
2026
+ }),
2027
+ init: (header, inheritedEventCount) => ({
2028
+ currentModel: null, currentProvider: null, last: null, byModel: {}, modelOrder: [],
2029
+ sessionId: typeof header?.id === 'string' ? header.id : '',
2030
+ inheritedEventCount: Number.isSafeInteger(inheritedEventCount) && inheritedEventCount >= 0 ? inheritedEventCount : 0,
2031
+ ownBoundaryKnown: (Number.isSafeInteger(inheritedEventCount) && inheritedEventCount >= 0) || !header?.isSeeded,
2032
+ ownChildIds: [],
2033
+ own: { currentModel: null, currentProvider: null, last: null, byModel: {}, modelOrder: [] },
2034
+ }),
2035
+ apply: (state, event) => {
2036
+ const full = foldUsage(state, event)
2037
+ const isOwn = state.ownBoundaryKnown && (state.inheritedEventCount === 0 || (Number.isSafeInteger(event.seq) && event.seq >= state.inheritedEventCount))
2038
+ // Inherited model context is useful; inherited usage and retry slots are not.
2039
+ const own = isOwn ? foldUsage(state.own, event) : { ...state.own, currentModel: full.currentModel, currentProvider: full.currentProvider }
2040
+ const childId = isOwn && event.type === 'subagent/catalog' ? event.data?.childId : null
2041
+ const ownChildIds = typeof childId === 'string' && !state.ownChildIds.includes(childId) ? [...state.ownChildIds, childId] : state.ownChildIds
2042
+ return { ...full, own, ownChildIds }
2030
2043
  },
2031
2044
  wire: {
2032
2045
  viewSchema: z.object({
@@ -2055,6 +2068,7 @@ export function makeCostProjection(configOrGetter, services) {
2055
2068
  mode: z.enum(['one-shot', 'continuable']),
2056
2069
  createdAt: z.number(),
2057
2070
  cost: z.number(),
2071
+ waiting: z.boolean().optional(),
2058
2072
  costByCurrency: z.record(z.string(), z.number().nonnegative()),
2059
2073
  currencyByModel: z.record(z.string(), z.string()),
2060
2074
  mixedCurrency: z.boolean(),
@@ -2071,7 +2085,7 @@ export function makeCostProjection(configOrGetter, services) {
2071
2085
  const cfg = getConfig()
2072
2086
  const mainCurrency = (cfg.currency ?? 'CNY').toUpperCase()
2073
2087
  // v1.4.0: 子代理消耗 (换行单独展示)。取不到服务/没有子代理 → 空数组。
2074
- const subagents = collectSubagentCosts(services, state.sessionId, summarize)
2088
+ const subagents = collectSubagentCosts(services, state.sessionId, summarize, state.ownChildIds)
2075
2089
  // 无事件时返回 waiting 标记, 客户端据此显示 "~—" 而非 "~¥0"
2076
2090
  if (state.modelOrder.length === 0) {
2077
2091
  return { models: [], currentModel: state.currentModel ?? null, currentProvider: state.currentProvider ?? null, cost: -1, costByModel: {}, costByCurrency: {}, currencyByModel: {}, mixedCurrency: false, tokens: { uncachedInput: 0, cacheRead: 0, cacheWrite: 0, output: 0 }, tokensByModel: {}, currency: mainCurrency, isPeak: isPeakTime(), waiting: true, subagents }
@@ -2085,7 +2099,7 @@ export function makeCostProjection(configOrGetter, services) {
2085
2099
  }
2086
2100
  },
2087
2101
  },
2088
- stateVersion: 2,
2102
+ stateVersion: 3,
2089
2103
  }
2090
2104
  }
2091
2105