opencode-visual-cache 1.6.0 → 1.6.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.
package/README.md CHANGED
@@ -46,7 +46,7 @@
46
46
  - **模型定价**:显示当前模型的输入 / 缓存读 / 缓存写单价(从 provider 配置动态读取)
47
47
  - **折叠面板**:主标题默认折叠,点击展开;明细、模型、分布各自独立折叠
48
48
  - **颜色自适应**:命中率 ≥85% 绿 · ≥70% 橙 · <70% 红,颜色从主题色自动去饱和
49
- - **Token 分布**:按角色(系统提示 / 用户 / Agent 指令 / Tool 调用 / Tool 结果)展示估算 Token 占比
49
+ - **Token 分布**:按角色(系统提示 / 用户 / 子代理指令 / Tool 调用 / Tool 结果)展示估算 Token 占比
50
50
  - **折叠记忆**:折叠状态持久化,重启后保持
51
51
  - **语言适配**:支持 中文 / English / 日本語 / 한국어,自动检测系统语言,`/cache-lang` 运行时切换,偏好持久化优先
52
52
  - **多币种**:通过 `/cache-currency` 切换货币,费用和节省同步换算
@@ -150,7 +150,7 @@ npm install -g opencode-visual-cache@latest
150
150
 
151
151
  通过 `/cache-section` 切换后即时生效,无需重启。此外,该命令还可以开关面板的**外边框**——关闭后内容会顶格显示,释放额外空间。
152
152
 
153
- > **关于 Token 分布数值**:分布面板中"总计"为最后一次 API 调用的精确 token 数,"系统提示"/"用户"等分项为字符级 BPE 估算值。分项之和通常小于总计,差值主要来自 OpenCode 运行时注入的系统提示组成部分,包括环境信息、Skill 目录、工具 Schema 定义等(详见 [`system.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/system.ts)、[`tools.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/tools.ts))。这些内容不在 agent 配置的 `prompt` 字段中,因此插件无法估算,属于预期行为。
153
+ > **关于 Token 分布数值**:分布面板中"推理" API 返回的**精确值**;"系统提示"/"用户"/"子代理指令"/"Tool 调用"/"Tool 结果"为**估算值**——API 仅返回 token 总量,无法拆分各内容类型,插件按内容类型收集文本后基于字符计数近似估算,数值仅供参考。OpenCode 运行时注入的系统提示内容(环境信息、Skill 目录、工具 Schema 定义等,详见 [`system.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/system.ts)、[`tools.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/tools.ts))不在此估算范围内。
154
154
 
155
155
  ### 4.4 余额查询
156
156
 
package/README_EN.md CHANGED
@@ -46,7 +46,7 @@ Interested in sub-agent monitoring? Check out [opencode-subagent-magazine](https
46
46
  - **Model Pricing**: Input / cache-read / cache-write per-million rates (read from provider config dynamically)
47
47
  - **Collapsible**: Main title collapsed by default; click to expand. Detail, model, and distribution sections fold independently
48
48
  - **Adaptive Colors**: ≥85% green · ≥70% orange · <70% red, auto-desaturated from current theme
49
- - **Token Distribution**: Per-role (system / user / agent instr / tool call / tool result) estimated token breakdown
49
+ - **Token Distribution**: Per-role (system / user / sub-agent instr / tool call / tool result) estimated token breakdown
50
50
  - **Persistent State**: Fold preferences and config remembered across restarts via api.kv
51
51
  - **Language**: Chinese / English / 日本語 / 한국어, auto-detects system locale, with `/cache-lang` for runtime switching — user preference takes priority over auto-detection
52
52
  - **Multi-currency**: Switch via `/cache-currency` — costs, savings, and per-million rates convert in real time
@@ -150,6 +150,8 @@ Three sub-sections can be toggled independently to save sidebar space:
150
150
 
151
151
  Toggled via `/cache-section` — takes effect instantly, no restart required. The same command also toggles the panel **border**; turning it off removes the outline and padding so content fills the full width.
152
152
 
153
+ > **About Token Dist. values**: "Reasoning" is an exact value from the API; the other rows (system / user / sub-agent instr / tool call / tool result) are **estimates** — the API only reports total token counts, not how they split across content types, so the plugin collects text per content type and approximates via character counting. Values are indicative only. OpenCode runtime-injected system prompt content (environment info, skill catalog, tool schema definitions — see [`system.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/system.ts), [`tools.ts`](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/tools.ts)) is not covered by these estimates.
154
+
153
155
  ### 4.4 Balance Query
154
156
 
155
157
  The panel can display account balance from multiple AI providers. With **auto-switch** enabled, the balance query follows the model provider of the current session automatically.
@@ -1 +1 @@
1
- export declare const PLUGIN_VERSION = "1.6.0";
1
+ export declare const PLUGIN_VERSION = "1.6.1";
package/dist/_version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION = "1.6.0";
2
+ export const PLUGIN_VERSION = "1.6.1";
package/dist/i18n.d.ts CHANGED
@@ -21,11 +21,11 @@ declare const ZH_T: {
21
21
  readonly distTitle: "估算 Token 分布";
22
22
  readonly distSys: "系统提示:";
23
23
  readonly distUser: "用户:";
24
- readonly distAgent: "Agent 指令:";
24
+ readonly distAgent: "子代理指令:";
25
25
  readonly distTool: "Tool 调用:";
26
26
  readonly distRes: "Tool 结果:";
27
- readonly distTotal: "总计:";
28
- readonly distOut: "输出:";
27
+ readonly distReason: "推理:";
28
+ readonly stepsCount: "本回合 {n} 次调用:";
29
29
  readonly secDetail: "明细";
30
30
  readonly secModel: "模型";
31
31
  readonly secSkills: "已加载技能";
package/dist/i18n.js CHANGED
@@ -24,11 +24,11 @@ const ZH_T = {
24
24
  distTitle: "估算 Token 分布",
25
25
  distSys: "系统提示:",
26
26
  distUser: "用户:",
27
- distAgent: "Agent 指令:",
27
+ distAgent: "子代理指令:",
28
28
  distTool: "Tool 调用:",
29
29
  distRes: "Tool 结果:",
30
- distTotal: "总计:",
31
- distOut: "输出:",
30
+ distReason: "推理:",
31
+ stepsCount: "本回合 {n} 次调用:",
32
32
  secDetail: "明细",
33
33
  secModel: "模型",
34
34
  secSkills: "已加载技能",
@@ -105,11 +105,11 @@ const EN_T = {
105
105
  distTitle: "Estimated Token Dist.",
106
106
  distSys: "System:",
107
107
  distUser: "User:",
108
- distAgent: "Agent Instr:",
108
+ distAgent: "Sub-Agent Instr:",
109
109
  distTool: "Tool Call:",
110
110
  distRes: "Tool Result:",
111
- distTotal: "Total:",
112
- distOut: "Output:",
111
+ distReason: "Reasoning:",
112
+ stepsCount: "Turn #{n} calls:",
113
113
  secDetail: "Detail",
114
114
  secModel: "Model",
115
115
  secSkills: "Loaded Skills",
@@ -186,11 +186,11 @@ const JA_T = {
186
186
  distTitle: "Token 分布(推定)",
187
187
  distSys: "システム:",
188
188
  distUser: "ユーザー:",
189
- distAgent: "Agent 指示:",
189
+ distAgent: "サブエージェント指示:",
190
190
  distTool: "Tool 呼び出し:",
191
191
  distRes: "Tool 結果:",
192
- distTotal: "合計:",
193
- distOut: "出力:",
192
+ distReason: "推論:",
193
+ stepsCount: "今回 {n} 回呼出:",
194
194
  secDetail: "明細",
195
195
  secModel: "モデル",
196
196
  secSkills: "読み込み済みスキル",
@@ -267,11 +267,11 @@ const KO_T = {
267
267
  distTitle: "Token 분포(추정)",
268
268
  distSys: "시스템:",
269
269
  distUser: "사용자:",
270
- distAgent: "Agent 지시:",
270
+ distAgent: "서브 에이전트 지시:",
271
271
  distTool: "Tool 호출:",
272
272
  distRes: "Tool 결과:",
273
- distTotal: "합계:",
274
- distOut: "출력:",
273
+ distReason: "추론:",
274
+ stepsCount: "이번 턴 {n}회 호출:",
275
275
  secDetail: "상세",
276
276
  secModel: "모델",
277
277
  secSkills: "로드된 스킬",
package/dist/index.js CHANGED
@@ -347,7 +347,7 @@ function TokenCachePanel(props) {
347
347
  // stable until the next successful computation arrives.
348
348
  const [lastDist, setLastDist] = createSignal({
349
349
  system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0,
350
- output: 0, apiOutput: 0, apiInput: 0, stepCost: 0,
350
+ output: 0, reasoning: 0, apiOutput: 0, apiInput: 0, stepCost: 0, stepCount: 0,
351
351
  });
352
352
  const [lastHasDist, setLastHasDist] = createSignal(false);
353
353
  const [dataSignal, setDataSignal] = createSignal({
@@ -355,7 +355,7 @@ function TokenCachePanel(props) {
355
355
  cost: 0, saved: 0, model: "", inputRate: 0, cacheReadRate: 0, cacheWriteRate: 0,
356
356
  hasPricing: false, hasData: false, trend: 0, hasTrendData: false,
357
357
  providerName: "", sessionHitRate: 0,
358
- dist: { system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0, output: 0, apiOutput: 0, apiInput: 0, stepCost: 0 },
358
+ dist: { system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0, output: 0, reasoning: 0, apiOutput: 0, apiInput: 0, stepCost: 0, stepCount: 0 },
359
359
  hasDistData: false,
360
360
  skills: [],
361
361
  hasSkills: false,
@@ -485,7 +485,7 @@ function TokenCachePanel(props) {
485
485
  const trend = hasTrendData ? lastMsgHitRate - prevMsgHitRate : 0, providerName = pid || "";
486
486
  // untrack 只包裹已知触发死锁的 API
487
487
  const distData = untrack(() => {
488
- let dist = { system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0, output: 0, apiOutput: 0, apiInput: 0, stepCost: 0 };
488
+ let dist = { system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0, output: 0, reasoning: 0, apiOutput: 0, apiInput: 0, stepCost: 0, stepCount: 0 };
489
489
  let hasDistData = false;
490
490
  const loadedSkills = new Map();
491
491
  try {
@@ -520,6 +520,7 @@ function TokenCachePanel(props) {
520
520
  else if (msg.role === "assistant") {
521
521
  const am = msg;
522
522
  dist.output += num(am.tokens?.output);
523
+ dist.reasoning += num(am.tokens?.reasoning);
523
524
  let parts = [];
524
525
  try {
525
526
  parts = props.api.state.part(msg.id);
@@ -535,6 +536,13 @@ function TokenCachePanel(props) {
535
536
  catch { }
536
537
  if (rawInput)
537
538
  dist.toolCall += estimateTokens(rawInput);
539
+ // 子代理委托(task 工具):任务描述计入子代理指令(1.15.x 无 subtask part)
540
+ if (tp.tool === "task" && tp.state?.input) {
541
+ const ti = tp.state.input;
542
+ const prompt = typeof ti.prompt === "string" ? ti.prompt : "";
543
+ const desc = typeof ti.description === "string" ? ti.description : "";
544
+ dist.agent += estimateTokens(prompt || desc);
545
+ }
538
546
  if (tp.state.status === "completed") {
539
547
  const c = tp.state;
540
548
  if (c.output)
@@ -565,8 +573,6 @@ function TokenCachePanel(props) {
565
573
  }
566
574
  }
567
575
  }
568
- else if (p.type === "reasoning")
569
- dist.agent += estimateTokens(p.text);
570
576
  else if (p.type === "subtask") {
571
577
  const sub = p;
572
578
  dist.agent += estimateTokens(sub.prompt || sub.description || "");
@@ -587,7 +593,36 @@ function TokenCachePanel(props) {
587
593
  // 取最后一条有数据消息的总输入(含缓存读/写)作为当前 context 大小
588
594
  dist.apiInput = num(lastAssMsg?.tokens?.input) + num(lastAssMsg?.tokens?.cache?.read) + num(lastAssMsg?.tokens?.cache?.write);
589
595
  dist.apiOutput = num(lastAssMsg?.tokens?.output);
590
- hasDistData = dist.system + dist.user + dist.agent + dist.toolCall + dist.toolResult > 0 || dist.apiOutput > 0 || dist.apiInput > 0;
596
+ // 本回合(最后一条有数据消息所在的 parentID 链)的 API 调用次数与末次成本。
597
+ // opencode 将回合内每次工具调用循环拆为独立 assistant 消息(各含 1 个 step-finish),
598
+ // 故按 parentID 链聚合统计,而非单条消息。
599
+ if (lastAssMsg) {
600
+ const roundParent = lastAssMsg.parentID;
601
+ let lastCost;
602
+ for (let i = msgs.length - 1; i >= 0; i--) {
603
+ const m = msgs[i];
604
+ if (m.role !== "assistant")
605
+ continue;
606
+ if (m.parentID !== roundParent)
607
+ break;
608
+ let parts = [];
609
+ try {
610
+ parts = props.api.state.part(m.id);
611
+ }
612
+ catch { }
613
+ for (const p of parts) {
614
+ if (p.type !== "step-finish")
615
+ continue;
616
+ dist.stepCount++;
617
+ const sc = p.cost;
618
+ if (lastCost === undefined && typeof sc === "number" && Number.isFinite(sc))
619
+ lastCost = sc;
620
+ }
621
+ }
622
+ if (lastCost !== undefined)
623
+ dist.stepCost = lastCost;
624
+ }
625
+ hasDistData = dist.system + dist.user + dist.agent + dist.toolCall + dist.toolResult > 0 || dist.apiOutput > 0 || dist.apiInput > 0 || dist.reasoning > 0;
591
626
  }
592
627
  catch { }
593
628
  const finalDist = hasDistData ? dist : lastDist(), finalHasDist = hasDistData || lastHasDist();
@@ -801,7 +836,7 @@ function TokenCachePanel(props) {
801
836
  const prefix = " \u21b3 " + t("subPrefix");
802
837
  const maxSidW = Math.max(6, panelWidth() - visualWidth(prefix));
803
838
  return (_jsxs("text", { children: [_jsx("span", { style: { fg: pal().muted }, children: prefix }), _jsx("span", { style: { fg: pal().text }, children: truncateVisual(props.signals.overrideSessionId(), maxSidW) })] }));
804
- })() }), _jsxs(Show, { when: data().hasData, fallback: _jsxs(_Fragment, { children: [_jsx("text", { fg: pal().muted, children: sep() }), _jsxs("text", { children: [_jsx("span", { style: { fg: pal().muted }, children: "> " }), _jsx("span", { style: { fg: pal().muted }, children: t("noData") })] })] }), children: [_jsx("text", { fg: pal().muted, children: sep() }), _jsxs("text", { children: [_jsxs("span", { style: { fg: pal().text }, children: [t("hit"), " "] }), _jsxs("span", { style: { fg: hitColor() }, children: ["[", bar(), "] "] }), _jsx("span", { style: { fg: pal().text }, children: pct() }), _jsx(Show, { when: data().hasTrendData, children: _jsxs("span", { style: { fg: Math.abs(data().trend) >= 0.05 ? (data().trend > 0 ? pal().success : pal().error) : pal().text }, children: [" ", trendLabel(data().trend)] }) })] }), _jsx("text", { fg: pal().muted, children: justify(t("totalHit"), (Math.floor(data().sessionHitRate * 10) / 10).toFixed(1) + "%") }), _jsxs(Show, { when: sectionDetail(), children: [_jsxs("text", { onMouseUp: () => setDetailOpen((o) => { const n = !o; persistFold("detail", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: detailOpen() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: t("secDetail") }) }), _jsx("span", { style: { fg: pal().muted }, children: sep().slice(visualWidth((detailOpen() ? "\u25bc " : "\u25b6 ") + t("secDetail"))) })] }), _jsxs(Show, { when: detailOpen(), children: [_jsx(Show, { when: data().read > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("read"), fmt(data().read), t("tok")) }) }), _jsx(Show, { when: data().write > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("write"), fmt(data().write), t("tok")) }) }), _jsx("text", { fg: pal().muted, children: justify(t("miss"), fmt(data().freshInput + data().write), t("tok")) }), _jsx("text", { fg: pal().muted, children: justify(t("out"), fmt(data().output), t("tok")) }), _jsx(Show, { when: data().saved > 0, children: _jsxs("text", { children: [_jsx("span", { style: { fg: pal().muted }, children: t("saved") }), _jsx("span", { children: " ".repeat(Math.max(1, panelWidth() - gutter() - visualWidth(t("saved")) - visualWidth("~" + fmtCost(data().saved, currencySymbol(), exchangeRate())))) }), _jsxs("span", { style: { fg: pal().success }, children: ["~", fmtCost(data().saved, currencySymbol(), exchangeRate())] })] }) })] })] }), _jsxs(Show, { when: sectionModel(), children: [_jsxs("text", { onMouseUp: () => setModelOpen((o) => { const n = !o; persistFold("model", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: modelOpen() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: t("secModel") }) }), _jsx("span", { style: { fg: pal().muted }, children: sep().slice(visualWidth((modelOpen() ? "\u25bc " : "\u25b6 ") + t("secModel"))) })] }), _jsxs(Show, { when: modelOpen(), children: [_jsx("text", { fg: pal().text, children: justify(t("cost"), fmtCost(data().cost, currencySymbol(), exchangeRate())) }), _jsx(Show, { when: data().providerName, children: _jsx("text", { fg: pal().muted, children: justify(t("provider"), data().providerName) }) }), _jsx("text", { fg: pal().muted, children: justify(t("model"), data().model) }), _jsxs(Show, { when: data().hasPricing, children: [_jsx("text", { fg: pal().muted, children: justify(t("rate"), currencySymbol() + (data().inputRate * exchangeRate()).toFixed(2) + "/M " + t("inputRate")) }), _jsx(Show, { when: data().cacheReadRate > 0, children: _jsx("text", { fg: pal().muted, children: justify("", currencySymbol() + (data().cacheReadRate * exchangeRate()).toFixed(2) + "/M " + t("cacheRate")) }) }), _jsx(Show, { when: data().cacheWriteRate > 0, children: _jsx("text", { fg: pal().muted, children: justify("", currencySymbol() + (data().cacheWriteRate * exchangeRate()).toFixed(2) + "/M " + t("writeRate")) }) })] })] })] }), _jsx(Show, { when: sectionDist(), children: _jsxs(Show, { when: data().hasDistData, children: [_jsxs("text", { onMouseUp: () => setDistOpen((o) => { const n = !o; persistFold("dist", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: distOpen() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: t("distTitle") }) }), _jsx("span", { style: { fg: pal().muted }, children: sep().slice(visualWidth((distOpen() ? "\u25bc " : "\u25b6 ") + t("distTitle"))) })] }), _jsxs(Show, { when: distOpen(), children: [_jsx(Show, { when: data().dist.system > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("distSys"), fmt(data().dist.system), t("tok")) }) }), _jsx(Show, { when: data().dist.user > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("distUser"), fmt(data().dist.user), t("tok")) }) }), _jsx(Show, { when: data().dist.agent > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("distAgent"), fmt(data().dist.agent), t("tok")) }) }), _jsx(Show, { when: data().dist.toolCall > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("distTool"), fmt(data().dist.toolCall), t("tok")) }) }), _jsx(Show, { when: data().dist.toolResult > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("distRes"), fmt(data().dist.toolResult), t("tok")) }) }), _jsx("text", { fg: pal().text, children: justify(t("distTotal"), fmt(data().dist.apiInput), t("tok")) })] })] }) }), _jsx(Show, { when: sectionSkills(), children: _jsxs(Show, { when: data().hasSkills, children: [_jsxs("text", { onMouseUp: () => setSkillsOpen((o) => { const n = !o; persistFold("skills", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: skillsOpen() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: t("secSkills") }) }), _jsxs("span", { style: { fg: pal().muted }, children: [" (", data().skills.length, ")"] }), _jsx("span", { style: { fg: pal().muted }, children: sep().slice(visualWidth((skillsOpen() ? "\u25bc " : "\u25b6 ") + t("secSkills") + ` (${data().skills.length})`)) })] }), _jsx(Show, { when: skillsOpen(), children: data().skills.map((sk) => {
839
+ })() }), _jsxs(Show, { when: data().hasData, fallback: _jsxs(_Fragment, { children: [_jsx("text", { fg: pal().muted, children: sep() }), _jsxs("text", { children: [_jsx("span", { style: { fg: pal().muted }, children: "> " }), _jsx("span", { style: { fg: pal().muted }, children: t("noData") })] })] }), children: [_jsx("text", { fg: pal().muted, children: sep() }), _jsxs("text", { children: [_jsxs("span", { style: { fg: pal().text }, children: [t("hit"), " "] }), _jsxs("span", { style: { fg: hitColor() }, children: ["[", bar(), "] "] }), _jsx("span", { style: { fg: pal().text }, children: pct() }), _jsx(Show, { when: data().hasTrendData, children: _jsxs("span", { style: { fg: Math.abs(data().trend) >= 0.05 ? (data().trend > 0 ? pal().success : pal().error) : pal().text }, children: [" ", trendLabel(data().trend)] }) })] }), _jsx("text", { fg: pal().muted, children: justify(t("totalHit"), (Math.floor(data().sessionHitRate * 10) / 10).toFixed(1) + "%") }), _jsxs(Show, { when: sectionDetail(), children: [_jsxs("text", { onMouseUp: () => setDetailOpen((o) => { const n = !o; persistFold("detail", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: detailOpen() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: t("secDetail") }) }), _jsx("span", { style: { fg: pal().muted }, children: sep().slice(visualWidth((detailOpen() ? "\u25bc " : "\u25b6 ") + t("secDetail"))) })] }), _jsxs(Show, { when: detailOpen(), children: [_jsx(Show, { when: data().read > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("read"), fmt(data().read), t("tok")) }) }), _jsx(Show, { when: data().write > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("write"), fmt(data().write), t("tok")) }) }), _jsx("text", { fg: pal().muted, children: justify(t("miss"), fmt(data().freshInput + data().write), t("tok")) }), _jsx("text", { fg: pal().muted, children: justify(t("out"), fmt(data().output), t("tok")) }), _jsx(Show, { when: data().dist.stepCount >= 2, children: _jsx("text", { fg: pal().muted, children: justify(t("stepsCount", { n: data().dist.stepCount }), fmtCost(data().dist.stepCost, currencySymbol(), exchangeRate())) }) }), _jsx(Show, { when: data().saved > 0, children: _jsxs("text", { children: [_jsx("span", { style: { fg: pal().muted }, children: t("saved") }), _jsx("span", { children: " ".repeat(Math.max(1, panelWidth() - gutter() - visualWidth(t("saved")) - visualWidth("~" + fmtCost(data().saved, currencySymbol(), exchangeRate())))) }), _jsxs("span", { style: { fg: pal().success }, children: ["~", fmtCost(data().saved, currencySymbol(), exchangeRate())] })] }) })] })] }), _jsxs(Show, { when: sectionModel(), children: [_jsxs("text", { onMouseUp: () => setModelOpen((o) => { const n = !o; persistFold("model", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: modelOpen() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: t("secModel") }) }), _jsx("span", { style: { fg: pal().muted }, children: sep().slice(visualWidth((modelOpen() ? "\u25bc " : "\u25b6 ") + t("secModel"))) })] }), _jsxs(Show, { when: modelOpen(), children: [_jsx("text", { fg: pal().text, children: justify(t("cost"), fmtCost(data().cost, currencySymbol(), exchangeRate())) }), _jsx(Show, { when: data().providerName, children: _jsx("text", { fg: pal().muted, children: justify(t("provider"), data().providerName) }) }), _jsx("text", { fg: pal().muted, children: justify(t("model"), data().model) }), _jsxs(Show, { when: data().hasPricing, children: [_jsx("text", { fg: pal().muted, children: justify(t("rate"), currencySymbol() + (data().inputRate * exchangeRate()).toFixed(2) + "/M " + t("inputRate")) }), _jsx(Show, { when: data().cacheReadRate > 0, children: _jsx("text", { fg: pal().muted, children: justify("", currencySymbol() + (data().cacheReadRate * exchangeRate()).toFixed(2) + "/M " + t("cacheRate")) }) }), _jsx(Show, { when: data().cacheWriteRate > 0, children: _jsx("text", { fg: pal().muted, children: justify("", currencySymbol() + (data().cacheWriteRate * exchangeRate()).toFixed(2) + "/M " + t("writeRate")) }) })] })] })] }), _jsx(Show, { when: sectionDist(), children: _jsxs(Show, { when: data().hasDistData, children: [_jsxs("text", { onMouseUp: () => setDistOpen((o) => { const n = !o; persistFold("dist", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: distOpen() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: t("distTitle") }) }), _jsx("span", { style: { fg: pal().muted }, children: sep().slice(visualWidth((distOpen() ? "\u25bc " : "\u25b6 ") + t("distTitle"))) })] }), _jsxs(Show, { when: distOpen(), children: [_jsx(Show, { when: data().dist.system > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("distSys"), fmt(data().dist.system), t("tok")) }) }), _jsx(Show, { when: data().dist.user > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("distUser"), fmt(data().dist.user), t("tok")) }) }), _jsx(Show, { when: data().dist.agent > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("distAgent"), fmt(data().dist.agent), t("tok")) }) }), _jsx(Show, { when: data().dist.toolCall > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("distTool"), fmt(data().dist.toolCall), t("tok")) }) }), _jsx(Show, { when: data().dist.toolResult > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("distRes"), fmt(data().dist.toolResult), t("tok")) }) }), _jsx(Show, { when: data().dist.reasoning > 0, children: _jsx("text", { fg: pal().muted, children: justify(t("distReason"), fmt(data().dist.reasoning), t("tok")) }) })] })] }) }), _jsx(Show, { when: sectionSkills(), children: _jsxs(Show, { when: data().hasSkills, children: [_jsxs("text", { onMouseUp: () => setSkillsOpen((o) => { const n = !o; persistFold("skills", n); return n; }), children: [_jsx("span", { style: { fg: pal().muted }, children: skillsOpen() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: t("secSkills") }) }), _jsxs("span", { style: { fg: pal().muted }, children: [" (", data().skills.length, ")"] }), _jsx("span", { style: { fg: pal().muted }, children: sep().slice(visualWidth((skillsOpen() ? "\u25bc " : "\u25b6 ") + t("secSkills") + ` (${data().skills.length})`)) })] }), _jsx(Show, { when: skillsOpen(), children: data().skills.map((sk) => {
805
840
  const rightW = visualWidth(fmt(sk.tokens)) + UNIT_GAP + visualWidth(t("tok"));
806
841
  const maxLabel = Math.max(4, panelWidth() - gutter() - rightW - 1);
807
842
  const label = truncateVisual(sk.name, maxLabel);