@gamegeek-saikel/dsh-cost-meter 0.2.1 → 0.3.0-alpha.1

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 (33) hide show
  1. package/lib/client.js +33 -67
  2. package/lib/client.js.map +1 -1
  3. package/lib/index.js +13 -22
  4. package/lib/types/client/AssistantCostChip.d.ts +8 -19
  5. package/lib/types/client/AssistantCostChip.d.ts.map +1 -1
  6. package/lib/types/client/AssistantCostChip.js +32 -38
  7. package/lib/types/client/CostPluginCard.d.ts +1 -1
  8. package/lib/types/client/CostPluginCard.d.ts.map +1 -1
  9. package/lib/types/client/CostView.d.ts +1 -1
  10. package/lib/types/client/CostView.d.ts.map +1 -1
  11. package/lib/types/client/SessionCostLine.d.ts +1 -4
  12. package/lib/types/client/SessionCostLine.d.ts.map +1 -1
  13. package/lib/types/client/SessionCostPill.d.ts +3 -3
  14. package/lib/types/client/SessionCostPill.d.ts.map +1 -1
  15. package/lib/types/client/SessionCostPill.js +8 -4
  16. package/lib/types/client/index.d.ts +8 -7
  17. package/lib/types/client/index.d.ts.map +1 -1
  18. package/lib/types/client/index.js +8 -5
  19. package/lib/types/index.d.ts +2 -1
  20. package/lib/types/index.d.ts.map +1 -1
  21. package/lib/types/index.js +14 -12
  22. package/lib/types/session-cost-projection.d.ts +9 -8
  23. package/lib/types/session-cost-projection.d.ts.map +1 -1
  24. package/lib/types/session-cost-projection.js +11 -17
  25. package/package.json +33 -29
  26. package/src/client/AssistantCostChip.tsx +37 -48
  27. package/src/client/CostPluginCard.tsx +1 -1
  28. package/src/client/CostView.tsx +1 -1
  29. package/src/client/SessionCostLine.tsx +1 -3
  30. package/src/client/SessionCostPill.tsx +12 -7
  31. package/src/client/index.ts +23 -12
  32. package/src/index.ts +15 -12
  33. package/src/session-cost-projection.ts +12 -17
package/lib/client.js CHANGED
@@ -323,28 +323,6 @@ window.__ModuleLoader__.load({
323
323
  if (offPeakTotal === 0) return null;
324
324
  return (peak.cacheReadPerMillion + peak.inputPerMillion + peak.outputPerMillion) / offPeakTotal;
325
325
  }
326
- /** CJK characters (the /1.5 density class). */
327
- const CJK_RE = /[\u3400-\u4dbf\u4e00-\u9fff]/g;
328
- /**
329
- * Heuristic character→token estimate: CJK ÷ 1.5, everything else ÷ 4
330
- * (the streaming estimate's output side).
331
- * @param text - the accumulated text.
332
- * @returns the estimated token count.
333
- */
334
- function estimateTokens(text) {
335
- const cjk = (text.match(CJK_RE) ?? []).length;
336
- return Math.ceil(cjk / 1.5 + (text.length - cjk) / 4);
337
- }
338
- /**
339
- * Tokens of the streamed text/reasoning blocks of a partial assistant.
340
- * @param blocks - the partial's content blocks (text/reasoning carry `text`).
341
- * @returns the estimated token count.
342
- */
343
- function blocksOutputTokens(blocks) {
344
- let tokens = 0;
345
- for (const block of blocks ?? []) if ((block.kind === "text" || block.kind === "reasoning") && typeof block.text === "string" && block.text.length > 0) tokens += estimateTokens(block.text);
346
- return tokens;
347
- }
348
326
  /**
349
327
  * The cache-read ratio of the newest completed step (input side of the
350
328
  * estimate). Absent history conservatively prices all input as uncached
@@ -819,44 +797,28 @@ window.__ModuleLoader__.load({
819
797
  //#endregion
820
798
  //#region src/client/AssistantCostChip.tsx
821
799
  /**
822
- * Per-reply cost chip: the anchored cost of ONE finalized reply. Rendered
823
- * through the assistant-actions slot but visually joined to the reply's
824
- * timing row (01:51 · 用时 · 首 token · tok/s): a flex `order` places it
825
- * after the clock text, and the type matches the clock's (label-tertiary,
826
- * 14px) with a dot separator, so it reads as part of the same metadata run.
827
- * The `messageId` owner prop maps to the assistant node's (turn, step),
828
- * which addresses the projection's step ledger — the cost is the anchored
829
- * snapshot value, never a current-price recompute. Unpriced replies render
800
+ * Per-reply cost at the end of the completed Turn's timing row. Rendered
801
+ * through the `conversation.chat.turnTail` chain slot — the selector only
802
+ * accepts closed Turns, so the extension appears once the reply settled and
803
+ * sits directly after the timing facts (用时 · 首 token · tok/s), with a dot
804
+ * separator and the same label-tertiary type as the clock text.
805
+ *
806
+ * DSH 0.1.5 removed the per-message assistant-actions slot and replaced the
807
+ * legacy chat-node window with turn/step locations; the cost is therefore
808
+ * addressed per Turn: the anchored step ledger (`sessionCost` projection) is
809
+ * filtered to the Turn's own steps and summed. Every price is the anchored
810
+ * snapshot value, never a current-price recompute; an unpriced Turn renders
830
811
  * `—` (the Cost tab explains why).
831
812
  */
832
- /**
833
- * Locate a finalized assistant node by its message id and return its
834
- * (turn, step) coordinates, or null when the node is out of window.
835
- * @param snapshot - the conversation snapshot.
836
- * @param messageId - the addressed message identity.
837
- * @returns the step coordinates, or null.
838
- */
839
- function stepOfMessage(snapshot, messageId) {
840
- const nodes = snapshot.chat.legacy.nodes;
841
- for (let i = nodes.length - 1; i >= 0; i -= 1) {
842
- const node = nodes[i];
843
- if (node?.kind === "assistant" && node.messageId === messageId && node.turn !== void 0) return {
844
- turn: node.turn,
845
- step: node.step
846
- };
847
- }
848
- return null;
849
- }
850
- /** The projection step for one (turn, step) coordinate, if the ledger has it. */
851
- function stepOf(steps, turn, step) {
852
- return steps?.find((entry) => entry.turn === turn && entry.step === step);
813
+ /** The Turn's own ledger steps, in fold order. */
814
+ function stepsOfTurn(steps, turn) {
815
+ return steps?.filter((entry) => entry.turn === turn) ?? [];
853
816
  }
854
- const AssistantCostChip = (0, react.memo)(function AssistantCostChip({ messageId, useSession, useProjection, t }) {
817
+ const AssistantCostChip = (0, react.memo)(function AssistantCostChip({ matched, useProjection, t }) {
855
818
  const currency = displayCurrency(t);
856
- const step = useSession((snapshot) => stepOfMessage(snapshot, messageId));
857
819
  const costCny = useProjection("sessionCost");
858
820
  const costUsd = useProjection("sessionCostUsd");
859
- const ledger = step === null ? void 0 : stepOf((currency === "USD" ? costUsd : costCny)?.steps, step.turn, step.step);
821
+ const cost = currency === "USD" ? costUsd : costCny;
860
822
  const [response, setResponse] = (0, react.useState)(null);
861
823
  (0, react.useEffect)(() => {
862
824
  let alive = true;
@@ -868,9 +830,11 @@ window.__ModuleLoader__.load({
868
830
  alive = false;
869
831
  };
870
832
  }, [currency]);
871
- if (ledger === void 0 || ledger.cost === null) {
872
- if (ledger === void 0) return null;
873
- const reason = ledger.unpricedReason === "NO_MODEL" ? t("reason.NO_MODEL") : t("reason.NO_PRICE");
833
+ const steps = stepsOfTurn(cost?.steps, matched.turn);
834
+ const priced = steps.filter((entry) => entry.cost !== null);
835
+ if (steps.length === 0) return null;
836
+ if (priced.length === 0) {
837
+ const reason = steps[0]?.unpricedReason === "NO_MODEL" ? t("reason.NO_MODEL") : t("reason.NO_PRICE");
874
838
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
875
839
  className: AssistantCostChip_module_css_default.unpriced,
876
840
  "data-cost-chip": true,
@@ -879,9 +843,12 @@ window.__ModuleLoader__.load({
879
843
  children: t("chip.unpriced")
880
844
  });
881
845
  }
882
- const amount = `${currencySymbol(currency)}${formatMoney(ledger.cost)}`;
883
- const band = ledger.band ?? bandForTime(ledger.time);
884
- const ratio = peakOffPeakMultiplier(response?.pricebook?.current ?? null, ledger.provider, ledger.model);
846
+ const total = priced.reduce((sum, entry) => sum + (entry.cost ?? 0), 0);
847
+ const amount = `${currencySymbol(currency)}${formatMoney(total)}`;
848
+ const last = priced[priced.length - 1];
849
+ if (last === void 0) return null;
850
+ const band = last.band ?? bandForTime(last.time);
851
+ const ratio = peakOffPeakMultiplier(response?.pricebook?.current ?? null, last.provider, last.model);
885
852
  const bandLabel = band === "peak" ? ratio === null ? t("band.peak") : t("price.peakRatio", { multiplier: formatMultiplier(ratio) }) : band === "offPeak" ? ratio === null ? t("band.offPeak") : t("price.offPeakRatio", { multiplier: formatMultiplier(1 / ratio) }) : null;
886
853
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
887
854
  className: AssistantCostChip_module_css_default.root,
@@ -934,7 +901,7 @@ window.__ModuleLoader__.load({
934
901
  * ratio (conservative all-uncached without history), output at the
935
902
  * character heuristic — both at CURRENT snapshot prices.
936
903
  */
937
- /** Read the conversation's running flag (the pill's estimate gate). */
904
+ /** Read the session's running flag (the pill's estimate gate). */
938
905
  function runningOf(snapshot) {
939
906
  return snapshot.running;
940
907
  }
@@ -970,7 +937,7 @@ window.__ModuleLoader__.load({
970
937
  const cost = currency === "USD" ? costUsd : costCny;
971
938
  const pressure = useProjection("contextPressure");
972
939
  const running = useSession(runningOf);
973
- const partial = useSession((snapshot) => snapshot.partial);
940
+ const partial = null;
974
941
  const [open, setOpen] = (0, react.useState)(false);
975
942
  const [response, setResponse] = (0, react.useState)(null);
976
943
  const wrapRef = (0, react.useRef)(null);
@@ -1004,7 +971,7 @@ window.__ModuleLoader__.load({
1004
971
  if (!running || projection === void 0) return null;
1005
972
  const bucket = bucketAt(snapshot ?? void 0, model?.provider, model?.model ?? "unknown", Date.now(), schedule);
1006
973
  if (bucket === void 0) return null;
1007
- const stepEstimate = estimateCost(bucket, pressure?.projectedTokens ?? pressure?.pressureTokens ?? 0, cacheReadRatioOf(projection.steps), blocksOutputTokens(partial?.blocks));
974
+ const stepEstimate = estimateCost(bucket, pressure?.projectedTokens ?? pressure?.pressureTokens ?? 0, cacheReadRatioOf(projection.steps), 0);
1008
975
  return (combined?.cost ?? 0) + stepEstimate;
1009
976
  }, [
1010
977
  running,
@@ -1485,10 +1452,9 @@ window.__ModuleLoader__.load({
1485
1452
  locale: NS,
1486
1453
  label: () => t("view.cost")
1487
1454
  }, CostView));
1488
- ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register({
1489
- name: "conversation.chat.assistant-actions",
1490
- id: "cost",
1491
- order: 20,
1455
+ ctx.slots.inject("conversation.chat.turnTail", () => ctx.slots.register({
1456
+ name: "conversation.chat.turnTail",
1457
+ select: (owner) => owner.turn.status === "closed" ? { turn: owner.turn.turn } : null,
1492
1458
  locale: NS
1493
1459
  }, AssistantCostChip));
1494
1460
  ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({