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 +2 -2
- package/README_EN.md +3 -1
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/i18n.d.ts +3 -3
- package/dist/i18n.js +12 -12
- package/dist/index.js +42 -7
- package/dist/tui.js +290 -240
- package/package.json +6 -3
- package/src/_version.ts +1 -1
- package/src/i18n.ts +12 -12
- package/src/index.tsx +49 -12
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
- **模型定价**:显示当前模型的输入 / 缓存读 / 缓存写单价(从 provider 配置动态读取)
|
|
47
47
|
- **折叠面板**:主标题默认折叠,点击展开;明细、模型、分布各自独立折叠
|
|
48
48
|
- **颜色自适应**:命中率 ≥85% 绿 · ≥70% 橙 · <70% 红,颜色从主题色自动去饱和
|
|
49
|
-
- **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 分布数值**:分布面板中"
|
|
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.
|
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PLUGIN_VERSION = "1.6.
|
|
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.
|
|
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: "
|
|
24
|
+
readonly distAgent: "子代理指令:";
|
|
25
25
|
readonly distTool: "Tool 调用:";
|
|
26
26
|
readonly distRes: "Tool 结果:";
|
|
27
|
-
readonly
|
|
28
|
-
readonly
|
|
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: "
|
|
27
|
+
distAgent: "子代理指令:",
|
|
28
28
|
distTool: "Tool 调用:",
|
|
29
29
|
distRes: "Tool 结果:",
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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: "
|
|
189
|
+
distAgent: "サブエージェント指示:",
|
|
190
190
|
distTool: "Tool 呼び出し:",
|
|
191
191
|
distRes: "Tool 結果:",
|
|
192
|
-
|
|
193
|
-
|
|
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: "
|
|
270
|
+
distAgent: "서브 에이전트 지시:",
|
|
271
271
|
distTool: "Tool 호출:",
|
|
272
272
|
distRes: "Tool 결과:",
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
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().
|
|
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);
|