dsh-context 0.59.0 → 0.59.2

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.
Files changed (3) hide show
  1. package/lib/client.js +170 -133
  2. package/lib/index.js +17 -7
  3. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -56,15 +56,16 @@ window.__ModuleLoader__.load({
56
56
  "stats.cacheHit": "缓存命中",
57
57
  "stats.cacheHitTip": "整个会话累计的缓存读取占计费输入的比例。",
58
58
  "stats.cost": "费用",
59
- "stats.costTip": "估算当前 Agent 与其所有子 Agent 的累计费用:单价取自 models.dev 刊例价,人民币按 1 元 = 0.15 美元换算,仅供参考。",
59
+ "stats.costTip": "估算的费用,含当前智能体及所有子智能体,仅供参考。",
60
60
  "stats.costTipDeepseek": "DeepSeek 高峰时段(北京时间工作日 9:00–12:00、14:00–18:00)按挂牌价计费,其余时段半价。",
61
+ "stats.costTipCny": "人民币按 1 元 = 0.15 美元换算。",
61
62
  "stats.subCost": "子 Agent 费用",
62
63
  "stats.subCostTip": "所有子 Agent 会话各自累计费用(同口径估算)的合计,不含当前 Agent 自身。",
63
- "stats.costPriceHead": "每百万 tokens 价格:",
64
- "stats.costPriceHeadPair": "每百万 tokens 价格(高峰|空闲):",
65
- "stats.costHit": "命中",
66
- "stats.costMiss": "未命中",
67
- "stats.costWrite": "写入",
64
+ "stats.costPriceHead": "每百万 Token 价格:",
65
+ "stats.costPriceBy": "按 {p} · {m} 在 models.dev 的刊登价格如上。",
66
+ "stats.costHit": "缓存输入",
67
+ "stats.costMiss": "未缓存输入",
68
+ "stats.costWrite": "缓存写入",
68
69
  "stats.costOut": "输出",
69
70
  "stats.costUnavailable": "模型价格暂不可用(自动重试中),费用暂无法估算。",
70
71
  "timing.title": "耗时统计",
@@ -442,16 +443,17 @@ window.__ModuleLoader__.load({
442
443
  "stats.cacheHit": "Cache Hit",
443
444
  "stats.cacheHitTip": "Cumulative cache-read share of billed input across the session.",
444
445
  "stats.cost": "Cost",
445
- "stats.costTip": "Estimates the cumulative cost of this agent and all its subagents: rates come from the models.dev list prices; the CNY display converts at 1 CNY = 0.15 USD. Prices are for reference only.",
446
+ "stats.costTip": "Estimated cost for this agent and all its subagents, for reference only.",
446
447
  "stats.costTipDeepseek": "DeepSeek bills peak windows (Beijing Time weekdays 09:00–12:00, 14:00–18:00) at list price; all other hours are half price.",
448
+ "stats.costTipCny": "The CNY display converts at 1 CNY = 0.15 USD.",
447
449
  "stats.subCost": "Subagent Cost",
448
450
  "stats.subCostTip": "Summed estimates of every subagent session’s own cumulative cost (same basis); the current agent is not included.",
449
451
  "stats.costPriceHead": "Per-1M-token rates:",
450
- "stats.costPriceHeadPair": "Per-1M-token rates (peak|off-peak):",
451
- "stats.costHit": "hit",
452
- "stats.costMiss": "miss",
453
- "stats.costWrite": "write",
454
- "stats.costOut": "output",
452
+ "stats.costPriceBy": "Priced as listed on models.dev for {p} · {m}.",
453
+ "stats.costHit": "Cache input",
454
+ "stats.costMiss": "Uncached input",
455
+ "stats.costWrite": "Cache write",
456
+ "stats.costOut": "Output",
455
457
  "stats.costUnavailable": "Model prices are unavailable (retrying automatically) — the cost cannot be estimated yet.",
456
458
  "timing.title": "Timing Stats",
457
459
  "timing.total": "Active Time",
@@ -5593,6 +5595,7 @@ window.__ModuleLoader__.load({
5593
5595
  const lower = mid.toLowerCase();
5594
5596
  const cand = {
5595
5597
  pid,
5598
+ mid,
5596
5599
  rate,
5597
5600
  primary
5598
5601
  };
@@ -5604,47 +5607,47 @@ window.__ModuleLoader__.load({
5604
5607
  return { byModel };
5605
5608
  }
5606
5609
  /**
5607
- * The rate shared by a candidate group, or null: the largest equal-rate
5608
- * group wins, a tie refuses. Agreeing mirrors are noise; disagreeing ones
5609
- * price nothing rather than guess.
5610
+ * The candidate carrying the rate shared by a candidate group, or null: the
5611
+ * largest equal-rate group wins, a tie refuses. Agreeing mirrors are noise;
5612
+ * disagreeing ones price nothing rather than guess.
5610
5613
  */
5611
- function majorityRate(cands) {
5614
+ function majorityCandidate(cands) {
5612
5615
  const groups = [];
5613
5616
  for (const c of cands) {
5614
- const g = groups.find((g) => sameRate(g.rate, c.rate));
5617
+ const g = groups.find((g) => sameRate(g.cand.rate, c.rate));
5615
5618
  if (g === void 0) groups.push({
5616
- rate: c.rate,
5619
+ cand: c,
5617
5620
  n: 1
5618
5621
  });
5619
5622
  else g.n++;
5620
5623
  }
5621
5624
  groups.sort((a, b) => b.n - a.n);
5622
- return groups.length > 1 && groups[0].n === groups[1].n ? null : groups[0].rate;
5625
+ return groups.length > 1 && groups[0].n === groups[1].n ? null : groups[0].cand;
5623
5626
  }
5624
5627
  /**
5625
- * Pick one candidate group's rate: the model's own vendor (unique or
5626
- * unanimous), then a provider the model id itself names (`deepseek-v4-flash`
5627
- * under `deepseek`), then a lone carrier. Everything else prices null.
5628
+ * Pick one candidate: the model's own vendor (unique or unanimous), then a
5629
+ * provider the model id itself names (`deepseek-v4-flash` under `deepseek`),
5630
+ * then a lone carrier. Everything else prices null.
5628
5631
  */
5629
5632
  function resolveCandidates(lower, cands, org) {
5630
5633
  if (org !== null) {
5631
5634
  const named = cands.filter((c) => c.pid === org || org.startsWith(c.pid + "-") || org.length >= 4 && org.startsWith(c.pid));
5632
5635
  if (named.length > 0) {
5633
- const rate = majorityRate(named);
5634
- if (rate !== null) return rate;
5636
+ const cand = majorityCandidate(named);
5637
+ if (cand !== null) return cand;
5635
5638
  }
5636
5639
  }
5637
5640
  const primaries = cands.filter((c) => c.primary);
5638
5641
  if (primaries.length > 0) {
5639
- const rate = majorityRate(primaries);
5640
- if (rate !== null) return rate;
5642
+ const cand = majorityCandidate(primaries);
5643
+ if (cand !== null) return cand;
5641
5644
  }
5642
5645
  const prefixed = cands.filter((c) => lower.startsWith(c.pid + "-") || lower.startsWith(c.pid + "/"));
5643
5646
  if (prefixed.length > 0) {
5644
- const rate = majorityRate(prefixed);
5645
- if (rate !== null) return rate;
5647
+ const cand = majorityCandidate(prefixed);
5648
+ if (cand !== null) return cand;
5646
5649
  }
5647
- return cands.length === 1 ? cands[0].rate : null;
5650
+ return cands.length === 1 ? cands[0] : null;
5648
5651
  }
5649
5652
  /**
5650
5653
  * Resolve a model id book-wide: the full id first, then its last `/`-segment
@@ -5658,34 +5661,29 @@ window.__ModuleLoader__.load({
5658
5661
  for (const key of slash > 0 ? [lower, lower.slice(slash + 1)] : [lower]) {
5659
5662
  const cands = index.byModel.get(key);
5660
5663
  if (cands === void 0) continue;
5661
- const rate = resolveCandidates(key, cands, org);
5662
- if (rate !== null) return rate;
5664
+ const cand = resolveCandidates(key, cands, org);
5665
+ if (cand !== null) return cand;
5663
5666
  }
5664
5667
  return null;
5665
5668
  }
5666
- /** One rate triple at the doubled peak rate (the tooltip's `peak | off` pair). */
5667
- function peakOf(rate) {
5668
- return {
5669
- hit: rate.hit * PEAK_FACTOR,
5670
- miss: rate.miss * PEAK_FACTOR,
5671
- write: rate.write * PEAK_FACTOR,
5672
- out: rate.out * PEAK_FACTOR
5673
- };
5674
- }
5675
5669
  /** One book branch (a provider's models), as far as runtime can prove it. */
5676
5670
  function branchOf(book, id) {
5677
5671
  const v = book[id];
5678
5672
  return v !== null && typeof v === "object" ? v : null;
5679
5673
  }
5680
5674
  /**
5681
- * One branch's model id → rates, as far as runtime can prove it: exact own
5682
- * key first (the book is untrusted wire data), then case-insensitively, then
5683
- * by id SUFFIX — dsh spells some models short (`k3`) where the registry
5684
- * namespaces them (`kimi-k3`). Several suffix candidates (e.g. `k3` vs a
5685
- * hypothetical `other-k3`) are ambiguous and price nothing.
5675
+ * One branch's lookup hit — the registry's own model id and its rates, as
5676
+ * far as runtime can prove it: exact own key first (the book is untrusted
5677
+ * wire data), then case-insensitively, then by id SUFFIX — dsh spells some
5678
+ * models short (`k3`) where the registry namespaces them (`kimi-k3`).
5679
+ * Several suffix candidates (e.g. `k3` vs a hypothetical `other-k3`) are
5680
+ * ambiguous and price nothing.
5686
5681
  */
5687
- function lookup(models, model) {
5688
- if (Object.hasOwn(models, model)) return models[model];
5682
+ function lookupFace(models, model) {
5683
+ if (Object.hasOwn(models, model)) return {
5684
+ mid: model,
5685
+ rate: models[model]
5686
+ };
5689
5687
  const m = model.toLowerCase();
5690
5688
  let found = null;
5691
5689
  let seen = null;
@@ -5694,24 +5692,63 @@ window.__ModuleLoader__.load({
5694
5692
  if (lower !== m && !lower.endsWith("-" + m)) continue;
5695
5693
  if (seen !== null && seen !== lower) return null;
5696
5694
  seen = lower;
5697
- found = models[id];
5695
+ found = {
5696
+ mid: id,
5697
+ rate: models[id]
5698
+ };
5698
5699
  }
5699
5700
  return found;
5700
5701
  }
5701
5702
  /**
5702
- * The book's rates for one folded (provider, model) bucket, or null when
5703
- * the book cannot price it: the dsh provider id resolves through
5704
- * modelsDevProviderOf (unmapped ids pass through) and prices by model id —
5705
- * exact, case-insensitive, or suffix. A provider the book does not carry
5706
- * falls back to the model-side resolution index, which names the vendor
5707
- * from data (org segment, own-vendor SDK, id prefix, lone carrier) — never
5708
- * from a per-model hardcode.
5709
- */
5710
- function priceOf(book, provider, model) {
5703
+ * One billed model's price: the USD rates plus the registry face — the
5704
+ * models.dev provider id and branch model id spelling — they resolved from
5705
+ * (what the stats board prints as the listing source).
5706
+ *
5707
+ * Resolution: the dsh provider id goes through modelsDevProviderOf (unmapped
5708
+ * ids pass through) and prices by model id — exact, case-insensitive, or
5709
+ * suffix. A provider the book does not carry falls back to the model-side
5710
+ * resolution index, which names the vendor from data (org segment, own-vendor
5711
+ * SDK, id prefix, lone carrier) — never from a per-model hardcode. The one
5712
+ * vendor-level seam: a `deepseek-` model id prices from DeepSeek's own
5713
+ * registry branch first — its listing is the first-party price book (cache
5714
+ * rates included), while the model-side tiers can hand the same id to a
5715
+ * self-named hosting provider whose flat listing bills cache hits as plain
5716
+ * input (azure's `deepseek-v4-pro`, issue #93) — the same specialness the
5717
+ * peak/off-peak split already grants DeepSeek.
5718
+ */
5719
+ function priceFaceOf(book, provider, model) {
5711
5720
  if (book === null || book === void 0) return null;
5712
- const direct = branchOf(book.prices, modelsDevProviderOf(provider));
5713
- if (direct !== null) return lookup(direct, model);
5714
- return resolveRate(book.index, model);
5721
+ const registryPid = modelsDevProviderOf(provider);
5722
+ const direct = branchOf(book.prices, registryPid);
5723
+ if (direct !== null) {
5724
+ const found = lookupFace(direct, model);
5725
+ return found === null ? null : {
5726
+ pid: registryPid,
5727
+ mid: found.mid,
5728
+ rate: found.rate
5729
+ };
5730
+ }
5731
+ if (model.toLowerCase().startsWith("deepseek-")) {
5732
+ const firstParty = branchOf(book.prices, "deepseek");
5733
+ if (firstParty !== null) {
5734
+ const official = lookupFace(firstParty, model);
5735
+ if (official !== null) return {
5736
+ pid: "deepseek",
5737
+ mid: official.mid,
5738
+ rate: official.rate
5739
+ };
5740
+ }
5741
+ }
5742
+ const cand = resolveRate(book.index, model);
5743
+ return cand === null ? null : {
5744
+ pid: cand.pid,
5745
+ mid: cand.mid,
5746
+ rate: cand.rate
5747
+ };
5748
+ }
5749
+ /** The book's rates for one folded (provider, model) bucket ({@link priceFaceOf} without the face). */
5750
+ function priceOf(book, provider, model) {
5751
+ return priceFaceOf(book, provider, model)?.rate ?? null;
5715
5752
  }
5716
5753
  /**
5717
5754
  * Price the session's cumulative billed-token totals. Cache reads bill at
@@ -5798,9 +5835,9 @@ window.__ModuleLoader__.load({
5798
5835
  function formatCost(amount, currency) {
5799
5836
  return (currency === "cny" ? "¥" : "$") + (amount >= 1 ? amount.toFixed(2) : amount.toPrecision(2));
5800
5837
  }
5801
- /** Price-list figure: the same money format as formatCost, trailing zeros trimmed (¥3.00 → ¥3, $0.0070 → $0.007). */
5838
+ /** Price-list figure: always two decimals (the tooltip's `¥ XX.XX` rate format). */
5802
5839
  function formatPriceRate(amount, currency) {
5803
- return formatCost(amount, currency).replace(/0+$/, "").replace(/\.$/, "");
5840
+ return (currency === "cny" ? "¥" : "$") + amount.toFixed(2);
5804
5841
  }
5805
5842
  //#endregion
5806
5843
  //#region node_modules/.pnpm/@opencode-ai+models@0.0.87/node_modules/@opencode-ai/models/dist/error.js
@@ -10747,7 +10784,7 @@ window.__ModuleLoader__.load({
10747
10784
  return function PluginInfo() {
10748
10785
  const [latest, setLatest] = (0, react.useState)(null);
10749
10786
  (0, react.useEffect)(() => {
10750
- if ("0.59.0".includes("-dev")) return;
10787
+ if ("0.59.2".includes("-dev")) return;
10751
10788
  let on = true;
10752
10789
  fetchLatestVersion().then((v) => {
10753
10790
  if (on && v) setLatest(v);
@@ -10756,8 +10793,8 @@ window.__ModuleLoader__.load({
10756
10793
  on = false;
10757
10794
  };
10758
10795
  }, []);
10759
- const update = latest !== null && isNewerVersion(latest, "0.59.0") ? latest : null;
10760
- const nameText = "dsh-context (v0.59.0)";
10796
+ const update = latest !== null && isNewerVersion(latest, "0.59.2") ? latest : null;
10797
+ const nameText = "dsh-context (v0.59.2)";
10761
10798
  const nameValue = [nameText];
10762
10799
  if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10763
10800
  className: "lc-pi-update",
@@ -11222,18 +11259,22 @@ window.__ModuleLoader__.load({
11222
11259
  * and the caller derives them from the collections on the inline generation
11223
11260
  * (`countsOfRecords`). The card itself never touches the collections.
11224
11261
  */
11262
+ /** The four billed buckets of a price block, in display order, with their label keys. */
11263
+ const BANDS = [
11264
+ ["hit", "stats.costHit"],
11265
+ ["miss", "stats.costMiss"],
11266
+ ["write", "stats.costWrite"],
11267
+ ["out", "stats.costOut"]
11268
+ ];
11225
11269
  /**
11226
- * The rate rows for the models this session actually billed — the usage
11227
- * keys priced against the book, in fold order. Hostile branches skip;
11228
- * unpriced models drop (their buckets simply do not contribute). The label
11229
- * carries the provider only when the session billed more than one; a model
11230
- * with an off-peak bucket (DeepSeek's period-based list) shows the
11231
- * peak | off-peak pair.
11270
+ * The billed models' price blocks — the usage keys priced against the book,
11271
+ * in fold order, each carrying the registry face (models.dev provider id ·
11272
+ * model id) its rates resolved from. Hostile branches skip; unpriced models
11273
+ * drop (their buckets simply do not contribute).
11232
11274
  */
11233
11275
  function priceRowsOf(usage, book) {
11234
11276
  if (usage === void 0 || book === null) return [];
11235
11277
  const rows = [];
11236
- const multi = Object.keys(usage).length > 1;
11237
11278
  for (const provider of Object.keys(usage)) {
11238
11279
  const models = asRecord(usage[provider]);
11239
11280
  /* v8 ignore next 1 -- the fold's inputs are mergeCostUsage's own output
@@ -11241,16 +11282,11 @@ window.__ModuleLoader__.load({
11241
11282
  reaches here; the guard stays for the helper's own contract. */
11242
11283
  if (models === null) continue;
11243
11284
  for (const model of Object.keys(models)) {
11244
- const rate = priceOf(book, provider, model);
11245
- if (rate === null) continue;
11246
- const periods = asRecord(models[model]);
11247
- const deepseek = isDeepSeekProvider(provider);
11248
- const billedOff = deepseek && periods !== null && periods.off !== void 0;
11285
+ const face = priceFaceOf(book, provider, model);
11286
+ if (face === null) continue;
11249
11287
  rows.push({
11250
11288
  key: provider + "/" + model,
11251
- label: multi && provider !== "" ? `${model} · ${provider}` : model,
11252
- rate: deepseek ? peakOf(rate) : rate,
11253
- ...billedOff ? { offRate: rate } : {}
11289
+ face
11254
11290
  });
11255
11291
  }
11256
11292
  }
@@ -11314,62 +11350,50 @@ window.__ModuleLoader__.load({
11314
11350
  const subCost = estimateSessionCost(subUsage, book, currency);
11315
11351
  const fmtRate = (usd) => formatPriceRate(toCurrency(usd, currency), currency);
11316
11352
  const rows = priceRowsOf(usage, book);
11353
+ const subRows = priceRowsOf(subUsage ?? void 0, book);
11317
11354
  const deepseek = usage !== void 0 && Object.keys(usage).some((p) => isDeepSeekProvider(p));
11318
11355
  const subDeepseek = subUsage !== null && Object.keys(subUsage).some((p) => isDeepSeekProvider(p));
11319
- const anyPair = rows.some((r) => r.offRate !== void 0);
11320
11356
  const unpriced = rows.length === 0 && usage !== void 0 && Object.keys(usage).length > 0 && (failed || book !== null);
11321
- const subUnpriced = subUsage !== null && priceRowsOf(subUsage, book).length === 0 && (failed || book !== null);
11322
- const costTip = [
11323
- t("stats.costTip") + (deepseek ? " " + t("stats.costTipDeepseek") : ""),
11324
- rows.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
11325
- className: "lc-stat-tip-prices",
11326
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11327
- className: "lc-stat-tip-head",
11328
- children: anyPair ? t("stats.costPriceHeadPair") : t("stats.costPriceHead")
11329
- }), rows.map((r) => {
11330
- const cells = [
11331
- [
11332
- t("stats.costHit"),
11333
- r.rate.hit,
11334
- r.offRate?.hit
11335
- ],
11336
- [
11337
- t("stats.costMiss"),
11338
- r.rate.miss,
11339
- r.offRate?.miss
11340
- ],
11341
- [
11342
- t("stats.costWrite"),
11343
- r.rate.write,
11344
- r.offRate?.write
11345
- ],
11346
- [
11347
- t("stats.costOut"),
11348
- r.rate.out,
11349
- r.offRate?.out
11350
- ]
11351
- ];
11352
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
11353
- className: "lc-stat-tip-row",
11354
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
11355
- className: "lc-stat-tip-model",
11356
- children: r.label
11357
- }), cells.map(([name, peak, off]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
11358
- " · ",
11359
- name,
11360
- " ",
11361
- off === void 0 ? fmtRate(peak) : `${fmtRate(peak)}|${fmtRate(off)}`
11362
- ] }, name))]
11363
- }, r.key);
11357
+ const subUnpriced = subRows.length === 0 && subUsage !== null && (failed || book !== null);
11358
+ const pricesBlock = (blocks) => blocks.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
11359
+ className: "lc-stat-tip-prices",
11360
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11361
+ className: "lc-stat-tip-head",
11362
+ children: t("stats.costPriceHead")
11363
+ }), blocks.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
11364
+ className: "lc-stat-tip-row",
11365
+ children: [BANDS.filter(([bucket]) => r.face.rate[bucket] > 0).map(([bucket, label]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
11366
+ className: "lc-stat-tip-band",
11367
+ children: [
11368
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { children: t(label) }),
11369
+ " ",
11370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: fmtRate(r.face.rate[bucket]) })
11371
+ ]
11372
+ }, bucket)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11373
+ className: "lc-stat-tip-by",
11374
+ children: t("stats.costPriceBy", {
11375
+ p: r.face.pid,
11376
+ m: r.face.mid
11377
+ })
11364
11378
  })]
11365
- }, "prices") : null,
11379
+ }, r.key))]
11380
+ }, "prices") : null;
11381
+ const notes = (deep) => [currency === "cny" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("stats.costTipCny") }, "cny") : null, deep ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("stats.costTipDeepseek") }, "peak") : null].filter((el) => el !== null);
11382
+ const costTip = [
11383
+ t("stats.costTip"),
11384
+ pricesBlock(rows),
11385
+ ...notes(deepseek),
11366
11386
  unpriced ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("stats.costUnavailable") }, "unavailable") : null
11367
11387
  ];
11368
- const subTip = [t("stats.subCostTip") + (subDeepseek ? " " + t("stats.costTipDeepseek") : ""), subUnpriced ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("stats.costUnavailable") }, "unavailable") : null];
11388
+ const subTip = [
11389
+ t("stats.subCostTip"),
11390
+ pricesBlock(subRows),
11391
+ ...notes(subDeepseek),
11392
+ subUnpriced ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("stats.costUnavailable") }, "unavailable") : null
11393
+ ];
11369
11394
  const hit = props.usage === null ? null : cacheHitPercent(numOf(props.usage.cacheReadTokens), numOf(props.usage.uncachedInputTokens) + numOf(props.usage.cacheReadTokens) + numOf(props.usage.cacheWriteTokens));
11370
- const cell = (label, value, tip) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11371
- className: "lc-stat" + (tip === void 0 ? "" : " lc-stat-tipped group/tip"),
11372
- children: [
11395
+ const cell = (label, value, tip, href) => {
11396
+ const body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
11373
11397
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
11374
11398
  className: "lc-stat-label",
11375
11399
  children: [label, tip !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
@@ -11387,8 +11411,21 @@ window.__ModuleLoader__.load({
11387
11411
  role: "tooltip",
11388
11412
  children: tip
11389
11413
  })
11390
- ]
11391
- });
11414
+ ] });
11415
+ const className = "lc-stat" + (tip === void 0 ? "" : " lc-stat-tipped group/tip");
11416
+ return href === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11417
+ className,
11418
+ children: body
11419
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
11420
+ className,
11421
+ href,
11422
+ target: "_blank",
11423
+ rel: "noreferrer noopener",
11424
+ children: body
11425
+ });
11426
+ };
11427
+ const costPids = new Set(rows.map((r) => r.face.pid).filter((p) => p !== ""));
11428
+ const costHref = costPids.size === 1 ? "https://models.dev/providers/" + [...costPids][0] + "/" : void 0;
11392
11429
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11393
11430
  className: "lc-card lc-col-stats flex-[3] min-w-[min(360px,100%)]",
11394
11431
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -11405,7 +11442,7 @@ window.__ModuleLoader__.load({
11405
11442
  cell(t("stats.humanInputs"), props.humanInputs ?? 0, t("stats.humanInputsTip")),
11406
11443
  cell(t("stats.toolCalls"), props.toolCalls ?? 0),
11407
11444
  cell(t("stats.cacheHit"), hit === null ? "—" : `${hit}%`, t("stats.cacheHitTip")),
11408
- cell(t("stats.cost"), cost === null ? "—" : formatCost(cost, currency), costTip),
11445
+ cell(t("stats.cost"), cost === null ? "—" : formatCost(cost, currency), costTip, costHref),
11409
11446
  cell(t("stats.subCost"), subCost === null ? "—" : formatCost(subCost, currency), subTip)
11410
11447
  ]
11411
11448
  })]
@@ -12320,7 +12357,7 @@ window.__ModuleLoader__.load({
12320
12357
  }
12321
12358
  //#endregion
12322
12359
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/stats.css.mjs
12323
- const css$13 = ".lc-stat{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex-direction:column;gap:2px;min-width:0;padding:6px;display:flex}.lc-stats .lc-stat{padding:6px 3px}.lc-stats .lc-stat-q{margin:0;position:absolute;top:3px;right:4px}.lc-stat-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;font-weight:600;overflow:hidden}.lc-stat-value{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:center;font-size:14px;font-weight:600;overflow:hidden}.lc-stat-sub{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;overflow:hidden}.lc-stat-tipped{position:relative}.lc-stat-q{text-align:center;width:11px;height:11px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:help;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:50%;margin-left:4px;font-size:9px;font-style:normal;font-weight:700;line-height:11px;display:inline-block}.lc-stat-tip{max-width:280px;font-weight:400;line-height:1.5;top:calc(100% + 6px);left:0}.lc-stat-tip-prices{border-top:1px dashed var(--dsw-alias-border-l1);margin-top:5px;padding-top:5px;display:block}.lc-stat-tip-head{font-weight:600;display:block}.lc-timing-tps{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);font-variant-numeric:tabular-nums;cursor:default;border-radius:999px;flex:none;margin-left:auto;padding:1px 8px;font-size:12px;line-height:18px}.lc-stat-tip-row{margin-top:2px;display:block}.lc-stat-tip-model{color:var(--dsw-alias-label-primary)}.lc-card-sub{color:var(--dsw-alias-label-secondary);margin-left:auto;font-size:12px;font-weight:400}.lc-overview-num{align-items:baseline;gap:6px;margin-bottom:8px;display:flex}.lc-overview-num>b{font-size:20px}.lc-overview-num span{color:var(--dsw-alias-label-secondary)}.lc-overview-pct{margin-left:auto;font-size:11px}.lc-overview-pct b{color:var(--dsw-alias-label-primary);margin-right:4px;font-size:20px}.lc-stat-head{color:var(--dsw-alias-label-secondary);margin:12px 0 8px;font-size:11px;font-weight:600}.lc-col-donut{flex-direction:column;display:flex}.lc-col-donut .lc-donut-row{flex:1}.lc-donut{flex:none;position:relative}.lc-donut svg{display:block}.lc-donut-track{stroke:color-mix(in srgb, var(--color-neutral-500) 18%, transparent)}.lc-donut-seg{transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out)}.lc-donut-dim .lc-donut-seg{opacity:.35}.lc-donut-dim .lc-donut-seg-on{opacity:1}.lc-donut-center{pointer-events:none;text-align:center;box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:1px;padding:0 7px;display:flex;position:absolute;inset:0}.lc-donut-center b{white-space:nowrap;text-overflow:ellipsis;max-width:100%;line-height:1.15;overflow:hidden}.lc-donut-center span{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-sl{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:4px 20px;min-width:0;display:grid;overflow:hidden}.lc-sl-row{min-width:0;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;flex-direction:column;gap:1px;margin:0 -6px;padding:1px 6px;font-size:12px;display:flex}.lc-sl-row-on{background:var(--dsw-alias-interactive-bg-hover)}.lc-sl-row-dim{opacity:.55}.lc-sl-main{align-items:center;gap:6px;min-width:0;display:flex}.lc-sl-dot{border-radius:2px;flex:none;width:8px;height:8px}.lc-sl-label{white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;font-weight:600;line-height:1.25;overflow:hidden}.lc-sl-pct{text-align:right;font-variant-numeric:tabular-nums;flex:none;min-width:34px;font-weight:700}.lc-sl-sub{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;text-overflow:ellipsis;margin-left:14px;font-size:11px;line-height:1.3;overflow:hidden}.lc-pi-grid{grid-template-columns:1fr;gap:8px;display:grid}.lc-pi-row-btn{appearance:none;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;width:100%;padding:0}.lc-pi-row{min-width:0;color:inherit;flex-direction:row;justify-content:space-between;align-items:baseline;gap:12px;text-decoration:none;display:flex}.lc-pi-hint{text-decoration:none}.lc-pi-update{color:var(--dsw-alias-state-warn-primary);white-space:nowrap;margin-left:6px;font-size:11px}.lc-pi-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;flex:none;font-size:11px;font-weight:600;overflow:hidden}.lc-pi-labelicon{vertical-align:-2px;margin-right:4px;display:inline-block}.lc-pi-value{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:right;font-size:13px;font-weight:600;overflow:hidden}";
12360
+ const css$13 = ".lc-stat{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex-direction:column;gap:2px;min-width:0;padding:6px;display:flex}a.lc-stat{color:inherit;cursor:pointer;text-decoration:none}a.lc-stat:hover{border-color:var(--dsw-alias-label-secondary)}.lc-stats .lc-stat{padding:6px 3px}.lc-stats .lc-stat-q{margin:0;position:absolute;top:3px;right:4px}.lc-stat-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;font-weight:600;overflow:hidden}.lc-stat-value{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:center;font-size:14px;font-weight:600;overflow:hidden}.lc-stat-sub{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;overflow:hidden}.lc-stat-tipped{position:relative}.lc-stat-q{text-align:center;width:11px;height:11px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:help;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:50%;margin-left:4px;font-size:9px;font-style:normal;font-weight:700;line-height:11px;display:inline-block}.lc-stat-tip{max-width:280px;font-weight:400;line-height:1.5;top:calc(100% + 6px);left:0}.lc-stat-tip-prices{border-top:1px dashed var(--dsw-alias-border-l1);margin-top:5px;padding-top:5px;display:block}.lc-stat-tip-head{font-weight:600;display:block}.lc-timing-tps{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);font-variant-numeric:tabular-nums;cursor:default;border-radius:999px;flex:none;margin-left:auto;padding:1px 8px;font-size:12px;line-height:18px}.lc-stat-tip-row{margin-top:2px;display:block}.lc-stat-tip-band{justify-content:space-between;gap:2rem;display:flex}.lc-stat-tip-band i{font-style:normal}.lc-stat-tip-by{color:var(--dsw-alias-label-secondary);margin-top:3px;display:block}.lc-card-sub{color:var(--dsw-alias-label-secondary);margin-left:auto;font-size:12px;font-weight:400}.lc-overview-num{align-items:baseline;gap:6px;margin-bottom:8px;display:flex}.lc-overview-num>b{font-size:20px}.lc-overview-num span{color:var(--dsw-alias-label-secondary)}.lc-overview-pct{margin-left:auto;font-size:11px}.lc-overview-pct b{color:var(--dsw-alias-label-primary);margin-right:4px;font-size:20px}.lc-stat-head{color:var(--dsw-alias-label-secondary);margin:12px 0 8px;font-size:11px;font-weight:600}.lc-col-donut{flex-direction:column;display:flex}.lc-col-donut .lc-donut-row{flex:1}.lc-donut{flex:none;position:relative}.lc-donut svg{display:block}.lc-donut-track{stroke:color-mix(in srgb, var(--color-neutral-500) 18%, transparent)}.lc-donut-seg{transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out)}.lc-donut-dim .lc-donut-seg{opacity:.35}.lc-donut-dim .lc-donut-seg-on{opacity:1}.lc-donut-center{pointer-events:none;text-align:center;box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:1px;padding:0 7px;display:flex;position:absolute;inset:0}.lc-donut-center b{white-space:nowrap;text-overflow:ellipsis;max-width:100%;line-height:1.15;overflow:hidden}.lc-donut-center span{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-sl{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:4px 20px;min-width:0;display:grid;overflow:hidden}.lc-sl-row{min-width:0;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;flex-direction:column;gap:1px;margin:0 -6px;padding:1px 6px;font-size:12px;display:flex}.lc-sl-row-on{background:var(--dsw-alias-interactive-bg-hover)}.lc-sl-row-dim{opacity:.55}.lc-sl-main{align-items:center;gap:6px;min-width:0;display:flex}.lc-sl-dot{border-radius:2px;flex:none;width:8px;height:8px}.lc-sl-label{white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;font-weight:600;line-height:1.25;overflow:hidden}.lc-sl-pct{text-align:right;font-variant-numeric:tabular-nums;flex:none;min-width:34px;font-weight:700}.lc-sl-sub{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;text-overflow:ellipsis;margin-left:14px;font-size:11px;line-height:1.3;overflow:hidden}.lc-pi-grid{grid-template-columns:1fr;gap:8px;display:grid}.lc-pi-row-btn{appearance:none;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;width:100%;padding:0}.lc-pi-row{min-width:0;color:inherit;flex-direction:row;justify-content:space-between;align-items:baseline;gap:12px;text-decoration:none;display:flex}.lc-pi-hint{text-decoration:none}.lc-pi-update{color:var(--dsw-alias-state-warn-primary);white-space:nowrap;margin-left:6px;font-size:11px}.lc-pi-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;flex:none;font-size:11px;font-weight:600;overflow:hidden}.lc-pi-labelicon{vertical-align:-2px;margin-right:4px;display:inline-block}.lc-pi-value{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:right;font-size:13px;font-weight:600;overflow:hidden}";
12324
12361
  const tagId$13 = "dsh-context/stats.css";
12325
12362
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$13) + "]") === null) {
12326
12363
  const tag = document.createElement("style");
package/lib/index.js CHANGED
@@ -1518,6 +1518,12 @@ function applyTimeline(state, event, bounds) {
1518
1518
  applySurface(s, event, event.type, data, asstMsg);
1519
1519
  break;
1520
1520
  }
1521
+ case "session/end-seed": {
1522
+ if (data?.inherited !== true) break;
1523
+ const s = ensure([]);
1524
+ s.cost = {};
1525
+ break;
1526
+ }
1521
1527
  case "plan/mode":
1522
1528
  if (data && typeof data.active === "boolean") {
1523
1529
  const s = ensure(["events"]);
@@ -1900,6 +1906,10 @@ function applyActivity(state, event) {
1900
1906
  delete next.stepStart;
1901
1907
  return next;
1902
1908
  }
1909
+ if (event.type === "session/end-seed") {
1910
+ if (event.data?.inherited !== true) return state;
1911
+ return { days: {} };
1912
+ }
1903
1913
  if (event.type !== "assistant/message") return state;
1904
1914
  const initiated = state.stepStart !== void 0 ? state.stepStart : event.time;
1905
1915
  const key = dayKeyOf(initiated);
@@ -1936,11 +1946,11 @@ function applyActivity(state, event) {
1936
1946
  /**
1937
1947
  * The daily-activity projection unit, registered alongside the timeline and
1938
1948
  * headers units (host/index.ts); the overview reads it through the session
1939
- * list's projection column. `stateVersion` 2: the ledger gained the per-day
1940
- * pricing records (and the fold the route/step-start tracking behind them),
1941
- * so on upgrade every cached row reads version-stale — the dashboard's
1942
- * warm-up cold-refolds it from the durable log, rebuilding the fees for the
1943
- * whole corpus exactly.
1949
+ * list's projection column. `stateVersion` 3: the fold resets the ledger at
1950
+ * the tagged fork/seed boundary, so a seeded session's days count post-seed
1951
+ * activity only (issue #94) — cached rows for seeded forks read
1952
+ * version-stale on upgrade and the dashboard's warm-up cold-refolds them
1953
+ * from the durable log, which drops the inherited prefix they booked.
1944
1954
  */
1945
1955
  function createContextActivityDefinition() {
1946
1956
  return {
@@ -1952,7 +1962,7 @@ function createContextActivityDefinition() {
1952
1962
  viewSchema: contextActivitySchema,
1953
1963
  view: (state) => ({ days: Object.fromEntries(Object.entries(state.days).map(([k, v]) => [k, { ...v }])) })
1954
1964
  },
1955
- stateVersion: 2
1965
+ stateVersion: 3
1956
1966
  };
1957
1967
  }
1958
1968
  /**
@@ -3281,7 +3291,7 @@ function createContextTimelineDefinition(config, slim) {
3281
3291
  },
3282
3292
  init: () => createTimelineState(),
3283
3293
  apply: (state, event) => applyTimeline(state, event, bounds),
3284
- stateVersion: 21
3294
+ stateVersion: 22
3285
3295
  };
3286
3296
  }
3287
3297
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.59.0",
3
+ "version": "0.59.2",
4
4
  "description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
5
5
  "icon": "icon.svg",
6
6
  "author": "bowenliang123",