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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-visual-cache",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "OpenCode TUI plugin displaying real-time token cache hit rate in the sidebar",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -54,8 +54,11 @@
|
|
|
54
54
|
"solid-js": ">=1.9.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@opencode-ai/plugin": "^1.
|
|
58
|
-
"@opencode-ai/sdk": "^1.
|
|
57
|
+
"@opencode-ai/plugin": "^1.18.16",
|
|
58
|
+
"@opencode-ai/sdk": "^1.18.16",
|
|
59
|
+
"@opentui/core": "^0.5.1",
|
|
60
|
+
"@opentui/solid": "^0.5.1",
|
|
61
|
+
"@opentui/keymap": "^0.5.1",
|
|
59
62
|
"esbuild": "^0.25.0",
|
|
60
63
|
"esbuild-plugin-solid": "^0.5.0",
|
|
61
64
|
"tsx": "^4.22.3",
|
package/src/_version.ts
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/src/i18n.ts
CHANGED
|
@@ -27,11 +27,11 @@ const ZH_T = {
|
|
|
27
27
|
distTitle: "估算 Token 分布",
|
|
28
28
|
distSys: "系统提示:",
|
|
29
29
|
distUser: "用户:",
|
|
30
|
-
distAgent: "
|
|
30
|
+
distAgent: "子代理指令:",
|
|
31
31
|
distTool: "Tool 调用:",
|
|
32
32
|
distRes: "Tool 结果:",
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
distReason: "推理:",
|
|
34
|
+
stepsCount: "本回合 {n} 次调用:",
|
|
35
35
|
secDetail: "明细",
|
|
36
36
|
secModel: "模型",
|
|
37
37
|
secSkills: "已加载技能",
|
|
@@ -112,11 +112,11 @@ const EN_T: Translation = {
|
|
|
112
112
|
distTitle: "Estimated Token Dist.",
|
|
113
113
|
distSys: "System:",
|
|
114
114
|
distUser: "User:",
|
|
115
|
-
distAgent: "Agent Instr:",
|
|
115
|
+
distAgent: "Sub-Agent Instr:",
|
|
116
116
|
distTool: "Tool Call:",
|
|
117
117
|
distRes: "Tool Result:",
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
distReason: "Reasoning:",
|
|
119
|
+
stepsCount: "Turn #{n} calls:",
|
|
120
120
|
secDetail: "Detail",
|
|
121
121
|
secModel: "Model",
|
|
122
122
|
secSkills: "Loaded Skills",
|
|
@@ -194,11 +194,11 @@ const JA_T: Translation = {
|
|
|
194
194
|
distTitle: "Token 分布(推定)",
|
|
195
195
|
distSys: "システム:",
|
|
196
196
|
distUser: "ユーザー:",
|
|
197
|
-
distAgent: "
|
|
197
|
+
distAgent: "サブエージェント指示:",
|
|
198
198
|
distTool: "Tool 呼び出し:",
|
|
199
199
|
distRes: "Tool 結果:",
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
distReason: "推論:",
|
|
201
|
+
stepsCount: "今回 {n} 回呼出:",
|
|
202
202
|
secDetail: "明細",
|
|
203
203
|
secModel: "モデル",
|
|
204
204
|
secSkills: "読み込み済みスキル",
|
|
@@ -276,11 +276,11 @@ const KO_T: Translation = {
|
|
|
276
276
|
distTitle: "Token 분포(추정)",
|
|
277
277
|
distSys: "시스템:",
|
|
278
278
|
distUser: "사용자:",
|
|
279
|
-
distAgent: "
|
|
279
|
+
distAgent: "서브 에이전트 지시:",
|
|
280
280
|
distTool: "Tool 호출:",
|
|
281
281
|
distRes: "Tool 결과:",
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
distReason: "추론:",
|
|
283
|
+
stepsCount: "이번 턴 {n}회 호출:",
|
|
284
284
|
secDetail: "상세",
|
|
285
285
|
secModel: "모델",
|
|
286
286
|
secSkills: "로드된 스킬",
|
package/src/index.tsx
CHANGED
|
@@ -259,13 +259,15 @@ function estimateTokens(text: string): number {
|
|
|
259
259
|
interface TokenDist {
|
|
260
260
|
system: number // UserMessage.system
|
|
261
261
|
user: number // user message text/file parts
|
|
262
|
-
agent: number //
|
|
262
|
+
agent: number // task tool input prompt/description (sub-agent delegation)
|
|
263
263
|
toolCall: number // ToolPart.input (actual tool params)
|
|
264
264
|
toolResult: number // ToolPart completed output / error
|
|
265
|
-
output: number // AssistantMessage.tokens.output (
|
|
265
|
+
output: number // AssistantMessage.tokens.output (API exact, reasoning excluded)
|
|
266
|
+
reasoning: number // AssistantMessage.tokens.reasoning (API exact)
|
|
266
267
|
apiOutput: number // StepFinishPart.tokens.output (API exact, preferred)
|
|
267
268
|
apiInput: number // API exact total input context (input + cache read + cache write)
|
|
268
|
-
stepCost: number
|
|
269
|
+
stepCost: number // last step-finish part cost (USD) in the current round
|
|
270
|
+
stepCount: number // step-finish parts count across the current round (parentID chain)
|
|
269
271
|
}
|
|
270
272
|
|
|
271
273
|
// ---------------------------------------------------------------------------
|
|
@@ -472,7 +474,7 @@ function TokenCachePanel(props: {
|
|
|
472
474
|
// stable until the next successful computation arrives.
|
|
473
475
|
const [lastDist, setLastDist] = createSignal<TokenDist>({
|
|
474
476
|
system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0,
|
|
475
|
-
output: 0, apiOutput: 0, apiInput: 0, stepCost: 0,
|
|
477
|
+
output: 0, reasoning: 0, apiOutput: 0, apiInput: 0, stepCost: 0, stepCount: 0,
|
|
476
478
|
})
|
|
477
479
|
const [lastHasDist, setLastHasDist] = createSignal(false)
|
|
478
480
|
|
|
@@ -481,7 +483,7 @@ function TokenCachePanel(props: {
|
|
|
481
483
|
cost: 0, saved: 0, model: "", inputRate: 0, cacheReadRate: 0, cacheWriteRate: 0,
|
|
482
484
|
hasPricing: false, hasData: false, trend: 0, hasTrendData: false,
|
|
483
485
|
providerName: "", sessionHitRate: 0,
|
|
484
|
-
dist: { system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0, output: 0, apiOutput: 0, apiInput: 0, stepCost: 0 },
|
|
486
|
+
dist: { system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0, output: 0, reasoning: 0, apiOutput: 0, apiInput: 0, stepCost: 0, stepCount: 0 },
|
|
485
487
|
hasDistData: false,
|
|
486
488
|
skills: [] as { name: string; tokens: number }[],
|
|
487
489
|
hasSkills: false,
|
|
@@ -599,7 +601,7 @@ function TokenCachePanel(props: {
|
|
|
599
601
|
|
|
600
602
|
// untrack 只包裹已知触发死锁的 API
|
|
601
603
|
const distData = untrack(() => {
|
|
602
|
-
let dist: TokenDist = { system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0, output: 0, apiOutput: 0, apiInput: 0, stepCost: 0 }
|
|
604
|
+
let dist: TokenDist = { system: 0, user: 0, agent: 0, toolCall: 0, toolResult: 0, output: 0, reasoning: 0, apiOutput: 0, apiInput: 0, stepCost: 0, stepCount: 0 }
|
|
603
605
|
let hasDistData = false
|
|
604
606
|
const loadedSkills = new Map<string, { name: string; tokens: number }>()
|
|
605
607
|
try {
|
|
@@ -621,12 +623,20 @@ function TokenCachePanel(props: {
|
|
|
621
623
|
} else if (msg.role === "assistant") {
|
|
622
624
|
const am = msg as AssistantMessage
|
|
623
625
|
dist.output += num(am.tokens?.output)
|
|
626
|
+
dist.reasoning += num(am.tokens?.reasoning)
|
|
624
627
|
let parts: readonly Part[] = []; try { parts = props.api.state.part(msg.id) } catch {}
|
|
625
628
|
for (const p of parts) {
|
|
626
629
|
if (p.type === "tool") {
|
|
627
630
|
const tp = p as any; let rawInput = ""
|
|
628
631
|
try { rawInput = tp.state.raw ?? (tp.state.input != null ? JSON.stringify(tp.state.input) : "") } catch {}
|
|
629
632
|
if (rawInput) dist.toolCall += estimateTokens(rawInput)
|
|
633
|
+
// 子代理委托(task 工具):任务描述计入子代理指令(1.15.x 无 subtask part)
|
|
634
|
+
if (tp.tool === "task" && tp.state?.input) {
|
|
635
|
+
const ti = tp.state.input
|
|
636
|
+
const prompt = typeof ti.prompt === "string" ? ti.prompt : ""
|
|
637
|
+
const desc = typeof ti.description === "string" ? ti.description : ""
|
|
638
|
+
dist.agent += estimateTokens(prompt || desc)
|
|
639
|
+
}
|
|
630
640
|
if (tp.state.status === "completed") { const c = tp.state; if (c.output) dist.toolResult += estimateTokens(c.output) }
|
|
631
641
|
else if (tp.state.status === "error") { const e = tp.state; if (e.error) dist.toolResult += estimateTokens(e.error) }
|
|
632
642
|
if (tp.tool === "skill" && tp.state.status === "completed") {
|
|
@@ -647,8 +657,7 @@ function TokenCachePanel(props: {
|
|
|
647
657
|
}
|
|
648
658
|
}
|
|
649
659
|
}
|
|
650
|
-
} else if (p.type === "
|
|
651
|
-
else if (p.type === "subtask") { const sub = p as any; dist.agent += estimateTokens(sub.prompt || sub.description || "") }
|
|
660
|
+
} else if (p.type === "subtask") { const sub = p as any; dist.agent += estimateTokens(sub.prompt || sub.description || "") }
|
|
652
661
|
}
|
|
653
662
|
}
|
|
654
663
|
}
|
|
@@ -661,7 +670,27 @@ function TokenCachePanel(props: {
|
|
|
661
670
|
// 取最后一条有数据消息的总输入(含缓存读/写)作为当前 context 大小
|
|
662
671
|
dist.apiInput = num(lastAssMsg?.tokens?.input) + num(lastAssMsg?.tokens?.cache?.read) + num(lastAssMsg?.tokens?.cache?.write)
|
|
663
672
|
dist.apiOutput = num(lastAssMsg?.tokens?.output)
|
|
664
|
-
|
|
673
|
+
// 本回合(最后一条有数据消息所在的 parentID 链)的 API 调用次数与末次成本。
|
|
674
|
+
// opencode 将回合内每次工具调用循环拆为独立 assistant 消息(各含 1 个 step-finish),
|
|
675
|
+
// 故按 parentID 链聚合统计,而非单条消息。
|
|
676
|
+
if (lastAssMsg) {
|
|
677
|
+
const roundParent = (lastAssMsg as AssistantMessage).parentID
|
|
678
|
+
let lastCost: number | undefined
|
|
679
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
680
|
+
const m = msgs[i]
|
|
681
|
+
if (m.role !== "assistant") continue
|
|
682
|
+
if ((m as AssistantMessage).parentID !== roundParent) break
|
|
683
|
+
let parts: readonly Part[] = []; try { parts = props.api.state.part(m.id) } catch {}
|
|
684
|
+
for (const p of parts) {
|
|
685
|
+
if (p.type !== "step-finish") continue
|
|
686
|
+
dist.stepCount++
|
|
687
|
+
const sc = (p as { cost?: unknown }).cost
|
|
688
|
+
if (lastCost === undefined && typeof sc === "number" && Number.isFinite(sc)) lastCost = sc
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (lastCost !== undefined) dist.stepCost = lastCost
|
|
692
|
+
}
|
|
693
|
+
hasDistData = dist.system + dist.user + dist.agent + dist.toolCall + dist.toolResult > 0 || dist.apiOutput > 0 || dist.apiInput > 0 || dist.reasoning > 0
|
|
665
694
|
} catch {}
|
|
666
695
|
const finalDist = hasDistData ? dist : lastDist(), finalHasDist = hasDistData || lastHasDist()
|
|
667
696
|
const skills = [...loadedSkills.values()]
|
|
@@ -976,6 +1005,12 @@ function TokenCachePanel(props: {
|
|
|
976
1005
|
<text fg={pal().muted}>
|
|
977
1006
|
{justify(t("out"), fmt(data().output), t("tok"))}
|
|
978
1007
|
</text>
|
|
1008
|
+
{/* 本回合多次 API 调用时才显示调用次数与末次成本(单次调用不占行) */}
|
|
1009
|
+
<Show when={data().dist.stepCount >= 2}>
|
|
1010
|
+
<text fg={pal().muted}>
|
|
1011
|
+
{justify(t("stepsCount", { n: data().dist.stepCount }), fmtCost(data().dist.stepCost, currencySymbol(), exchangeRate()))}
|
|
1012
|
+
</text>
|
|
1013
|
+
</Show>
|
|
979
1014
|
<Show when={data().saved > 0}>
|
|
980
1015
|
<text>
|
|
981
1016
|
<span style={{ fg: pal().muted }}>{t("saved")}</span>
|
|
@@ -1058,9 +1093,11 @@ function TokenCachePanel(props: {
|
|
|
1058
1093
|
{justify(t("distRes"), fmt(data().dist.toolResult), t("tok"))}
|
|
1059
1094
|
</text>
|
|
1060
1095
|
</Show>
|
|
1061
|
-
<
|
|
1062
|
-
{
|
|
1063
|
-
|
|
1096
|
+
<Show when={data().dist.reasoning > 0}>
|
|
1097
|
+
<text fg={pal().muted}>
|
|
1098
|
+
{justify(t("distReason"), fmt(data().dist.reasoning), t("tok"))}
|
|
1099
|
+
</text>
|
|
1100
|
+
</Show>
|
|
1064
1101
|
</Show>
|
|
1065
1102
|
</Show>
|
|
1066
1103
|
</Show>
|