opencode-visual-cache 1.6.2 → 1.7.0-beta.0
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/LICENSE +21 -21
- package/README.md +247 -247
- package/README_EN.md +247 -247
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/core/color.d.ts +37 -0
- package/dist/core/color.js +108 -0
- package/dist/core/currency.d.ts +17 -0
- package/dist/core/currency.js +43 -0
- package/dist/core/estimate.d.ts +1 -0
- package/dist/core/estimate.js +40 -0
- package/dist/core/format.d.ts +15 -0
- package/dist/core/format.js +90 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +5 -0
- package/dist/core/types.d.ts +17 -0
- package/dist/core/types.js +1 -0
- package/dist/index.js +12 -818
- package/dist/panel/TokenCachePanel.d.ts +10 -0
- package/dist/panel/TokenCachePanel.js +549 -0
- package/dist/panel/panel-api.d.ts +119 -0
- package/dist/panel/panel-api.js +1 -0
- package/dist/tui.js +223 -209
- package/dist/v2/commands.d.ts +10 -0
- package/dist/v2/commands.js +490 -0
- package/dist/v2/data.d.ts +27 -0
- package/dist/v2/data.js +103 -0
- package/dist/v2/index.d.ts +4 -0
- package/dist/v2/index.js +163 -0
- package/dist/v2/sidebar.d.ts +6 -0
- package/dist/v2/sidebar.js +36 -0
- package/dist/v2/status.d.ts +14 -0
- package/dist/v2/status.js +83 -0
- package/dist/v2/theme.d.ts +8 -0
- package/dist/v2/theme.js +16 -0
- package/dist/v2/types.d.ts +219 -0
- package/dist/v2/types.js +6 -0
- package/dist/v2/v2-panel-api.d.ts +8 -0
- package/dist/v2/v2-panel-api.js +176 -0
- package/dist/v2.js +2941 -0
- package/package.json +70 -67
- package/src/_version.ts +1 -1
- package/src/balance-providers.ts +153 -153
- package/src/core/color.ts +108 -0
- package/src/core/currency.ts +46 -0
- package/src/core/estimate.ts +36 -0
- package/src/core/format.ts +81 -0
- package/src/core/index.ts +5 -0
- package/src/core/types.ts +17 -0
- package/src/i18n.ts +380 -380
- package/src/index.tsx +1035 -2120
- package/src/panel/TokenCachePanel.tsx +776 -0
- package/src/panel/panel-api.ts +104 -0
- package/src/server.ts +10 -10
- package/src/v2/commands.ts +471 -0
- package/src/v2/data.ts +112 -0
- package/src/v2/index.tsx +184 -0
- package/src/v2/sidebar.tsx +69 -0
- package/src/v2/status.tsx +96 -0
- package/src/v2/theme.ts +19 -0
- package/src/v2/types.ts +159 -0
- package/src/v2/v2-panel-api.ts +177 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V2 (opencode2) context → PanelApi 适配实现。
|
|
3
|
+
* 核心能力(session/messages/tokens/storage/event/renderer/part)完整映射;
|
|
4
|
+
* 无 V2 对应的能力(config)以空值兜底。
|
|
5
|
+
*/
|
|
6
|
+
export function createPanelApi(context) {
|
|
7
|
+
// storage.store 持久化 → PanelApi.kv 语义
|
|
8
|
+
const kvStore = new Map();
|
|
9
|
+
// 消息索引:messages() 填充,part() 按 messageID 查(V2 无全局 message.get)
|
|
10
|
+
const messageIndex = new Map();
|
|
11
|
+
/** V2 content part → V1 Part 形状(TokenCachePanel 分布/技能区块消费)。
|
|
12
|
+
* V2 tool 输出在 state.content[](Content = TextContent | FileContent),
|
|
13
|
+
* 归一化为 V1 的 state.output 字符串;input 为字符串时保持(Streaming 态)。 */
|
|
14
|
+
const toV1Part = (p) => {
|
|
15
|
+
if (p.type === "tool") {
|
|
16
|
+
const st = { ...(p.state ?? {}) };
|
|
17
|
+
if (st.output === undefined && Array.isArray(st.content)) {
|
|
18
|
+
st.output = st.content
|
|
19
|
+
.map((c) => c.type === "text" ? c.text : c.type === "file" ? String(c.path ?? c.filename ?? "") : JSON.stringify(c))
|
|
20
|
+
.filter((t) => typeof t === "string" && t.length > 0)
|
|
21
|
+
.join("\n");
|
|
22
|
+
}
|
|
23
|
+
return { type: "tool", tool: p.tool ?? p.name, state: st, subagent_type: p.subagent_type, metadata: p.metadata };
|
|
24
|
+
}
|
|
25
|
+
if (p.type === "text") {
|
|
26
|
+
return { type: "text", text: String(p.text ?? ""), synthetic: p.synthetic, ignored: p.ignored };
|
|
27
|
+
}
|
|
28
|
+
if (p.type === "file") {
|
|
29
|
+
return { type: "file", source: p.source ?? {} };
|
|
30
|
+
}
|
|
31
|
+
if (p.type === "reasoning") {
|
|
32
|
+
return { type: "reasoning", text: String(p.text ?? "") };
|
|
33
|
+
}
|
|
34
|
+
return p;
|
|
35
|
+
};
|
|
36
|
+
const kvGet = (key, fallback) => {
|
|
37
|
+
let entry = kvStore.get(key);
|
|
38
|
+
if (!entry) {
|
|
39
|
+
// 首次访问:创建 storage.store(V2 恢复磁盘持久化值),避免设置重启丢失
|
|
40
|
+
const created = context.storage.store(`opencode-visual-cache.${key}`, {
|
|
41
|
+
initial: { value: fallback },
|
|
42
|
+
});
|
|
43
|
+
entry = created;
|
|
44
|
+
kvStore.set(key, entry);
|
|
45
|
+
}
|
|
46
|
+
const v = entry[0].value;
|
|
47
|
+
return v === undefined ? fallback : v;
|
|
48
|
+
};
|
|
49
|
+
const kvSet = (key, value) => {
|
|
50
|
+
let entry = kvStore.get(key);
|
|
51
|
+
if (!entry) {
|
|
52
|
+
const created = context.storage.store(`opencode-visual-cache.${key}`, {
|
|
53
|
+
initial: { value },
|
|
54
|
+
});
|
|
55
|
+
entry = created;
|
|
56
|
+
kvStore.set(key, entry);
|
|
57
|
+
}
|
|
58
|
+
const [, mutate] = entry;
|
|
59
|
+
return mutate((d) => { d.value = value; });
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
kv: {
|
|
63
|
+
ready: true,
|
|
64
|
+
get: kvGet,
|
|
65
|
+
set: kvSet,
|
|
66
|
+
},
|
|
67
|
+
state: {
|
|
68
|
+
session: {
|
|
69
|
+
get(id) {
|
|
70
|
+
const s = context.data.session.get(id);
|
|
71
|
+
if (!s)
|
|
72
|
+
return undefined;
|
|
73
|
+
const model = s.model;
|
|
74
|
+
return {
|
|
75
|
+
id: s.id,
|
|
76
|
+
title: s.title,
|
|
77
|
+
agent: s.agent,
|
|
78
|
+
model: model ? { providerID: model.providerID, id: model.id } : undefined,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
messages(id) {
|
|
82
|
+
// 归一化为 V1 消息形状(role/providerID/tokens/cost),组件体零改动;
|
|
83
|
+
// 同时建立 messageID → 消息索引供 part() 使用。
|
|
84
|
+
// 回合边界用 V2 官方规则(rows.ts reduceSessionRows):assistant 消息
|
|
85
|
+
// 的 finish 非 tool-calls/unknown 或 error 即回合终点;同回合消息注入
|
|
86
|
+
// 相同合成 parentID(V2 消息无 parentID),使 V1 组件按 parentID 链
|
|
87
|
+
// 聚合"本回合调用次数/末次成本"的逻辑成立。
|
|
88
|
+
const raw = context.data.session.message.list(id) ?? [];
|
|
89
|
+
let turnId;
|
|
90
|
+
const normalized = raw.map((m) => {
|
|
91
|
+
const record = m;
|
|
92
|
+
messageIndex.set(String(m.id), record);
|
|
93
|
+
let parentID;
|
|
94
|
+
if (record.type === "assistant") {
|
|
95
|
+
if (!turnId)
|
|
96
|
+
turnId = String(m.id);
|
|
97
|
+
parentID = turnId;
|
|
98
|
+
const finish = record.finish;
|
|
99
|
+
const terminal = (typeof finish === "string" && !["tool-calls", "unknown"].includes(finish)) || Boolean(record.error);
|
|
100
|
+
if (terminal)
|
|
101
|
+
turnId = undefined;
|
|
102
|
+
}
|
|
103
|
+
const model = m.model;
|
|
104
|
+
return {
|
|
105
|
+
...m,
|
|
106
|
+
parentID,
|
|
107
|
+
role: m.type === "assistant" ? "assistant" : m.type === "user" ? "user" : m.type,
|
|
108
|
+
providerID: model?.providerID,
|
|
109
|
+
modelID: model?.id,
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
return normalized;
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
// V2 Provider.Info 无 models——从独立模型列表组装 V1 形状({ id, models: { [modelID]: { cost } } })。
|
|
116
|
+
// Model.Info.cost 是数组(tier 分段定价),取第一档近似(无 tier 匹配信息)。
|
|
117
|
+
// 响应式 getter:每次访问实时组装(对齐官方 dialog-model 的 data.location.model.list(location.ref) 用法,
|
|
118
|
+
// location 数据为 Solid store——组件 effect 中读取自动建立依赖,数据同步后重算)。
|
|
119
|
+
get provider() {
|
|
120
|
+
const models = (context.data.location.model?.list(context.location) ?? []);
|
|
121
|
+
const byProvider = new Map();
|
|
122
|
+
for (const m of models) {
|
|
123
|
+
const pid = String(m.providerID ?? "");
|
|
124
|
+
if (!pid)
|
|
125
|
+
continue;
|
|
126
|
+
const costArr = Array.isArray(m.cost) ? m.cost : [];
|
|
127
|
+
const cost = costArr[0];
|
|
128
|
+
if (!cost)
|
|
129
|
+
continue;
|
|
130
|
+
const entry = byProvider.get(pid) ?? { id: pid, models: {} };
|
|
131
|
+
// session.model.id 可能是 modelID 纯名 / 完整引用(provider/model)/ 短名——多 key 注册保证命中
|
|
132
|
+
const modelID = String(m.modelID ?? "");
|
|
133
|
+
const fullId = m.id !== undefined ? String(m.id) : "";
|
|
134
|
+
const shortId = fullId.split("/").pop() ?? "";
|
|
135
|
+
for (const k of new Set([modelID, fullId, shortId].filter((x) => x.length > 0))) {
|
|
136
|
+
entry.models[k] = { cost };
|
|
137
|
+
}
|
|
138
|
+
byProvider.set(pid, entry);
|
|
139
|
+
}
|
|
140
|
+
return [...byProvider.values()];
|
|
141
|
+
},
|
|
142
|
+
config: {}, // V2 插件 API 无 config 读取;实验兜底
|
|
143
|
+
part(messageID) {
|
|
144
|
+
// V2 无 part API:从消息 content 构造 V1 Part 形状(token 分布/技能区块依赖)。
|
|
145
|
+
// user/synthetic/system 消息无 content——文本在顶层 text,构造 text part。
|
|
146
|
+
const msg = messageIndex.get(String(messageID));
|
|
147
|
+
if (!msg)
|
|
148
|
+
return [];
|
|
149
|
+
if (!Array.isArray(msg.content) || msg.content.length === 0) {
|
|
150
|
+
if (typeof msg.text === "string" && msg.text.length > 0) {
|
|
151
|
+
return [{ type: "text", text: msg.text }];
|
|
152
|
+
}
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
const parts = msg.content.map((p) => toV1Part(p));
|
|
156
|
+
// V2 无 step-finish part:assistant 消息的顶层 cost 合成一个(V1 每条消息一个 step-finish,
|
|
157
|
+
// 组件按 parentID 链聚合统计本回合调用次数与末次成本)
|
|
158
|
+
if (msg.type === "assistant" && typeof msg.cost === "number" && Number.isFinite(msg.cost)) {
|
|
159
|
+
parts.push({ type: "step-finish", cost: msg.cost });
|
|
160
|
+
}
|
|
161
|
+
return parts;
|
|
162
|
+
},
|
|
163
|
+
path: { directory: String(context.location?.directory ?? "") },
|
|
164
|
+
},
|
|
165
|
+
event: {
|
|
166
|
+
on: (type, handler) => context.data.on(type, handler),
|
|
167
|
+
},
|
|
168
|
+
renderer: { terminalWidth: context.renderer.terminalWidth },
|
|
169
|
+
keys: { formatBindings: () => undefined },
|
|
170
|
+
tuiConfig: {
|
|
171
|
+
keybinds: {
|
|
172
|
+
get: (command) => context.keymap.shortcuts(command),
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|