opencode-visual-cache 1.7.0-beta.3 → 1.7.0-beta.5

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.
@@ -1 +1 @@
1
- export declare const PLUGIN_VERSION = "1.7.0-beta.3";
1
+ export declare const PLUGIN_VERSION = "1.7.0-beta.5";
package/dist/_version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION = "1.7.0-beta.3";
2
+ export const PLUGIN_VERSION = "1.7.0-beta.5";
@@ -103,8 +103,34 @@ const moonshotProvider = {
103
103
  return [{ currency: "CNY", total: String(balance) }];
104
104
  },
105
105
  };
106
+ const hyperProvider = {
107
+ id: "hyper",
108
+ name: "Charm Hyper",
109
+ keyPlaceholder: "sk-hyper-...",
110
+ async fetchBalance(apiKey, signal) {
111
+ // 官方接口:GET /v1/credits → {"balance": 98}(积分余额)
112
+ const res = await fetch("https://hyper.charm.land/v1/credits", {
113
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
114
+ signal,
115
+ });
116
+ if (!res.ok) {
117
+ if (res.status === 401)
118
+ throw new BalanceError("401");
119
+ if (res.status === 403)
120
+ throw new BalanceError("403");
121
+ throw new BalanceError(String(res.status));
122
+ }
123
+ const json = await res.json();
124
+ const balance = json.balance;
125
+ if (typeof balance === "undefined" || balance === null)
126
+ throw new BalanceError("EMPTY");
127
+ // hyper 积分换算:100 积分 = $5,即 1 积分 = $0.05
128
+ const usd = (Number(balance) * 0.05).toFixed(2);
129
+ return [{ currency: "USD", total: usd }];
130
+ },
131
+ };
106
132
  /** 已注册的 provider 列表(按需追加新适配器)。 */
107
- export const balanceProviders = [deepseekProvider, siliconflowProvider, openrouterProvider, moonshotProvider];
133
+ export const balanceProviders = [deepseekProvider, siliconflowProvider, openrouterProvider, moonshotProvider, hyperProvider];
108
134
  /** 按 id 取 provider;未知 id 回退到第一个。 */
109
135
  export function getBalanceProvider(id) {
110
136
  return balanceProviders.find((p) => p.id === id) ?? balanceProviders[0] ?? deepseekProvider;
package/dist/index.js CHANGED
@@ -425,7 +425,10 @@ const tui = async (api) => {
425
425
  order: 55,
426
426
  slots: {
427
427
  session_prompt(_ctx, input) {
428
- return (_jsx(api.ui.Prompt, { sessionID: input.session_id, visible: input.visible, disabled: input.disabled, onSubmit: input.on_submit, ref: input.ref, hint: _jsx(BottomStatusBar, { api: api, signals: signals, sessionId: input.session_id }) }));
428
+ return (_jsx(api.ui.Prompt, { sessionID: input.session_id, visible: input.visible, disabled: input.disabled, onSubmit: input.on_submit, ref: input.ref, hint: _jsx(BottomStatusBar, { api: api, signals: signals, sessionId: input.session_id }),
429
+ // 接管 session_prompt 后需透传宿主的 session_prompt_right 插槽,
430
+ // 否则 oc-tps 等依赖该插槽的插件无法显示;无注册时 Slot 为 null。
431
+ right: _jsx(api.ui.Slot, { name: "session_prompt_right", session_id: input.session_id }) }));
429
432
  },
430
433
  },
431
434
  });
package/dist/tui.js CHANGED
@@ -95,7 +95,28 @@ var moonshotProvider = {
95
95
  return [{ currency: "CNY", total: String(balance) }];
96
96
  }
97
97
  };
98
- var balanceProviders = [deepseekProvider, siliconflowProvider, openrouterProvider, moonshotProvider];
98
+ var hyperProvider = {
99
+ id: "hyper",
100
+ name: "Charm Hyper",
101
+ keyPlaceholder: "sk-hyper-...",
102
+ async fetchBalance(apiKey, signal) {
103
+ const res = await fetch("https://hyper.charm.land/v1/credits", {
104
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
105
+ signal
106
+ });
107
+ if (!res.ok) {
108
+ if (res.status === 401) throw new BalanceError("401");
109
+ if (res.status === 403) throw new BalanceError("403");
110
+ throw new BalanceError(String(res.status));
111
+ }
112
+ const json = await res.json();
113
+ const balance = json.balance;
114
+ if (typeof balance === "undefined" || balance === null) throw new BalanceError("EMPTY");
115
+ const usd = (Number(balance) * 0.05).toFixed(2);
116
+ return [{ currency: "USD", total: usd }];
117
+ }
118
+ };
119
+ var balanceProviders = [deepseekProvider, siliconflowProvider, openrouterProvider, moonshotProvider, hyperProvider];
99
120
  function getBalanceProvider(id) {
100
121
  return balanceProviders.find((p) => p.id === id) ?? balanceProviders[0] ?? deepseekProvider;
101
122
  }
@@ -684,7 +705,7 @@ import { createElement as _$createElement } from "@opentui/solid";
684
705
  import { createMemo, createSignal, createEffect, onMount, onCleanup, Show, untrack } from "solid-js";
685
706
 
686
707
  // src/_version.ts
687
- var PLUGIN_VERSION = "1.7.0-beta.3";
708
+ var PLUGIN_VERSION = "1.7.0-beta.5";
688
709
 
689
710
  // src/panel/TokenCachePanel.tsx
690
711
  var MIN_PANEL_WIDTH = 20;
@@ -2415,6 +2436,14 @@ var tui = async (api) => {
2415
2436
  return input.session_id;
2416
2437
  }
2417
2438
  });
2439
+ },
2440
+ get right() {
2441
+ return _$createComponent2(api.ui.Slot, {
2442
+ name: "session_prompt_right",
2443
+ get session_id() {
2444
+ return input.session_id;
2445
+ }
2446
+ });
2418
2447
  }
2419
2448
  });
2420
2449
  }
package/dist/v2/index.js CHANGED
@@ -154,7 +154,7 @@ const mod = {
154
154
  // 底部状态栏(完整口径与 V1 一致;余额读共享 signals.balanceState)
155
155
  context.ui.slot({
156
156
  append: "prompt.footer.status",
157
- render: (props) => (_jsx(StatusView, { context: context, signals: signals, sessionID: String(props.sessionID ?? "") })),
157
+ render: (props) => (_jsx(StatusView, { context: context, api: api, signals: signals, sessionID: String(props.sessionID ?? "") })),
158
158
  });
159
159
  // 偏好持久化(实验:storage.store 用法验证)
160
160
  context.storage.store("opencode-visual-cache.panel", { initial: { collapsed: false } });
@@ -1,14 +1,16 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
2
  import type { Context } from "./types";
3
- import type { PanelSignals } from "../panel/panel-api";
3
+ import type { PanelApi, PanelSignals } from "../panel/panel-api";
4
4
  /**
5
5
  * 底部状态栏(prompt.footer.status)——对齐 V1 BottomStatusBar 统计段口径:
6
6
  * 单条命中率(最后一条有 token 的 assistant 消息)+ 趋势 + Tokens 总量 + 余额。
7
7
  * 余额读共享 signals.balanceState(PluginRoot 驱动轮询);provider 不支持余额时隐藏余额段(对齐 V1)。
8
8
  * 颜色经 mapTheme(V1 形状)——与侧边栏命中率颜色同源,保证两处一致。
9
+ * 显隐受 signals.sectionBottom 控制(/cache-section 切换),启动时从 kv 恢复偏好(对齐 V1)。
9
10
  */
10
11
  export declare function StatusView(props: {
11
12
  context: Context;
13
+ api: PanelApi;
12
14
  signals: PanelSignals;
13
15
  sessionID: string;
14
16
  }): import("solid-js").JSX.Element;
package/dist/v2/status.js CHANGED
@@ -1,18 +1,28 @@
1
1
  import { jsx as _jsx } from "@opentui/solid/jsx-runtime";
2
2
  /** @jsxImportSource @opentui/solid */
3
- import { createMemo, For, Show } from "solid-js";
3
+ import { createMemo, For, onMount, Show } from "solid-js";
4
4
  import { collectLastHitRate } from "./data";
5
5
  import { desaturateTo, fmtCompact, formatBalanceText, MAX_SAT, FALLBACK, visualWidth } from "../core";
6
6
  import { createT } from "../i18n";
7
7
  import { mapTheme } from "./theme";
8
+ const KV_PREFIX = "cache_panel";
8
9
  /**
9
10
  * 底部状态栏(prompt.footer.status)——对齐 V1 BottomStatusBar 统计段口径:
10
11
  * 单条命中率(最后一条有 token 的 assistant 消息)+ 趋势 + Tokens 总量 + 余额。
11
12
  * 余额读共享 signals.balanceState(PluginRoot 驱动轮询);provider 不支持余额时隐藏余额段(对齐 V1)。
12
13
  * 颜色经 mapTheme(V1 形状)——与侧边栏命中率颜色同源,保证两处一致。
14
+ * 显隐受 signals.sectionBottom 控制(/cache-section 切换),启动时从 kv 恢复偏好(对齐 V1)。
13
15
  */
14
16
  export function StatusView(props) {
15
17
  const t = createT(() => props.signals.langCode());
18
+ // 恢复显隐偏好(默认显示;关闭时隐藏统计段,与宿主默认 hint 行一致)
19
+ onMount(() => {
20
+ try {
21
+ const v = props.api.kv.get(`${KV_PREFIX}.section.bottom`, true);
22
+ props.signals.setSectionBottom(v !== false);
23
+ }
24
+ catch { }
25
+ });
16
26
  // 与侧边栏 TokenCachePanel 同一颜色来源(mapTheme → desaturateTo)
17
27
  const pal = createMemo(() => {
18
28
  const th = mapTheme(props.context.theme);
@@ -79,5 +89,5 @@ export function StatusView(props) {
79
89
  w += visualWidth(sg.text);
80
90
  return w;
81
91
  });
82
- return (_jsx(Show, { when: segsW() > 0, children: _jsx("text", { children: _jsx(For, { each: segs(), children: (sg) => _jsx("span", { style: { fg: sg.color }, children: sg.text }) }) }) }));
92
+ return (_jsx(Show, { when: segsW() > 0 && stats().hitRate >= 0 && props.signals.sectionBottom(), children: _jsx("text", { children: _jsx(For, { each: segs(), children: (sg) => _jsx("span", { style: { fg: sg.color }, children: sg.text }) }) }) }));
83
93
  }
package/dist/v2.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/v2/index.tsx
2
2
  import { createComponent as _$createComponent3 } from "@opentui/solid";
3
- import { createSignal as createSignal2, createEffect as createEffect2, onMount as onMount2, onCleanup as onCleanup2, untrack as untrack2 } from "solid-js";
3
+ import { createSignal as createSignal2, createEffect as createEffect2, onMount as onMount3, onCleanup as onCleanup2, untrack as untrack2 } from "solid-js";
4
4
 
5
5
  // src/v2/v2-panel-api.ts
6
6
  function createPanelApi(context) {
@@ -252,7 +252,28 @@ var moonshotProvider = {
252
252
  return [{ currency: "CNY", total: String(balance) }];
253
253
  }
254
254
  };
255
- var balanceProviders = [deepseekProvider, siliconflowProvider, openrouterProvider, moonshotProvider];
255
+ var hyperProvider = {
256
+ id: "hyper",
257
+ name: "Charm Hyper",
258
+ keyPlaceholder: "sk-hyper-...",
259
+ async fetchBalance(apiKey, signal) {
260
+ const res = await fetch("https://hyper.charm.land/v1/credits", {
261
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
262
+ signal
263
+ });
264
+ if (!res.ok) {
265
+ if (res.status === 401) throw new BalanceError("401");
266
+ if (res.status === 403) throw new BalanceError("403");
267
+ throw new BalanceError(String(res.status));
268
+ }
269
+ const json = await res.json();
270
+ const balance = json.balance;
271
+ if (typeof balance === "undefined" || balance === null) throw new BalanceError("EMPTY");
272
+ const usd = (Number(balance) * 0.05).toFixed(2);
273
+ return [{ currency: "USD", total: usd }];
274
+ }
275
+ };
276
+ var balanceProviders = [deepseekProvider, siliconflowProvider, openrouterProvider, moonshotProvider, hyperProvider];
256
277
  function getBalanceProvider(id) {
257
278
  return balanceProviders.find((p) => p.id === id) ?? balanceProviders[0] ?? deepseekProvider;
258
279
  }
@@ -822,7 +843,7 @@ function estimateTokens(text) {
822
843
  }
823
844
 
824
845
  // src/_version.ts
825
- var PLUGIN_VERSION = "1.7.0-beta.3";
846
+ var PLUGIN_VERSION = "1.7.0-beta.5";
826
847
 
827
848
  // src/panel/TokenCachePanel.tsx
828
849
  var MIN_PANEL_WIDTH = 20;
@@ -964,7 +985,7 @@ function TokenCachePanel(props) {
964
985
  lastMainSid = sid;
965
986
  if (props.signals.overrideSessionId()) {
966
987
  props.signals.setOverrideSessionId(void 0);
967
- props.api.kv.set(`${KV_PREFIX3}.session`, "");
988
+ props.api.kv.set(`${KV_PREFIX4}.session`, "");
968
989
  }
969
990
  }
970
991
  });
@@ -1196,7 +1217,7 @@ function TokenCachePanel(props) {
1196
1217
  });
1197
1218
  setLastHasDist(true);
1198
1219
  try {
1199
- props.api.kv.set(`${KV_PREFIX3}.dist_snapshot`, {
1220
+ props.api.kv.set(`${KV_PREFIX4}.dist_snapshot`, {
1200
1221
  ...d.dist
1201
1222
  });
1202
1223
  } catch {
@@ -1204,53 +1225,53 @@ function TokenCachePanel(props) {
1204
1225
  }
1205
1226
  });
1206
1227
  const [partVersion, setPartVersion] = createSignal(0);
1207
- const KV_PREFIX3 = "cache_panel";
1228
+ const KV_PREFIX4 = "cache_panel";
1208
1229
  const persistFold = (key, val) => {
1209
1230
  try {
1210
- props.api.kv.set(`${KV_PREFIX3}.${key}`, val);
1231
+ props.api.kv.set(`${KV_PREFIX4}.${key}`, val);
1211
1232
  } catch {
1212
1233
  }
1213
1234
  };
1214
1235
  onMount(() => {
1215
1236
  setPanelWidth(DEFAULT_PANEL_WIDTH);
1216
1237
  try {
1217
- setOpen(Boolean(props.api.kv.get(`${KV_PREFIX3}.open`, false)));
1218
- setDetailOpen(Boolean(props.api.kv.get(`${KV_PREFIX3}.detail`, true)));
1219
- setModelOpen(Boolean(props.api.kv.get(`${KV_PREFIX3}.model`, true)));
1220
- setDistOpen(Boolean(props.api.kv.get(`${KV_PREFIX3}.dist`, false)));
1221
- setSkillsOpen(Boolean(props.api.kv.get(`${KV_PREFIX3}.skills`, true)));
1238
+ setOpen(Boolean(props.api.kv.get(`${KV_PREFIX4}.open`, false)));
1239
+ setDetailOpen(Boolean(props.api.kv.get(`${KV_PREFIX4}.detail`, true)));
1240
+ setModelOpen(Boolean(props.api.kv.get(`${KV_PREFIX4}.model`, true)));
1241
+ setDistOpen(Boolean(props.api.kv.get(`${KV_PREFIX4}.dist`, false)));
1242
+ setSkillsOpen(Boolean(props.api.kv.get(`${KV_PREFIX4}.skills`, true)));
1222
1243
  } catch {
1223
1244
  }
1224
1245
  const doRestore = () => {
1225
1246
  try {
1226
- const sym = props.api.kv.get(`${KV_PREFIX3}.currency`);
1227
- const rate = props.api.kv.get(`${KV_PREFIX3}.rate`);
1247
+ const sym = props.api.kv.get(`${KV_PREFIX4}.currency`);
1248
+ const rate = props.api.kv.get(`${KV_PREFIX4}.rate`);
1228
1249
  if (typeof sym === "string") setCurrencySymbol(sym);
1229
1250
  if (typeof rate === "number" && rate > 0) setExchangeRate(rate);
1230
- const balCur = props.api.kv.get(`${KV_PREFIX3}.balance_currency`);
1251
+ const balCur = props.api.kv.get(`${KV_PREFIX4}.balance_currency`);
1231
1252
  if (typeof balCur === "string") setBalanceCurrency(balCur);
1232
- const savedProvider = props.api.kv.get(`${KV_PREFIX3}.balance.provider`);
1253
+ const savedProvider = props.api.kv.get(`${KV_PREFIX4}.balance.provider`);
1233
1254
  if (typeof savedProvider === "string" && balanceProviders.some((p) => p.id === savedProvider)) {
1234
1255
  setBalanceProviderId(savedProvider);
1235
1256
  setBalanceUnsupported(false);
1236
1257
  }
1237
- const savedAuto = props.api.kv.get(`${KV_PREFIX3}.balance.auto`);
1258
+ const savedAuto = props.api.kv.get(`${KV_PREFIX4}.balance.auto`);
1238
1259
  if (typeof savedAuto === "boolean") setAutoBalance(savedAuto);
1239
- const legacyKey = props.api.kv.get(`${KV_PREFIX3}.ds_key`, "");
1260
+ const legacyKey = props.api.kv.get(`${KV_PREFIX4}.ds_key`, "");
1240
1261
  if (legacyKey) {
1241
- const dsKey = props.api.kv.get(`${KV_PREFIX3}.balance.deepseek.key`, "");
1242
- if (!dsKey) props.api.kv.set(`${KV_PREFIX3}.balance.deepseek.key`, legacyKey);
1243
- props.api.kv.set(`${KV_PREFIX3}.ds_key`, "");
1262
+ const dsKey = props.api.kv.get(`${KV_PREFIX4}.balance.deepseek.key`, "");
1263
+ if (!dsKey) props.api.kv.set(`${KV_PREFIX4}.balance.deepseek.key`, legacyKey);
1264
+ props.api.kv.set(`${KV_PREFIX4}.ds_key`, "");
1244
1265
  }
1245
1266
  props.signals.setBalanceRefresh(props.signals.balanceRefresh() + 1);
1246
- setSectionDetail(Boolean(props.api.kv.get(`${KV_PREFIX3}.section.detail`, true)));
1247
- setSectionModel(Boolean(props.api.kv.get(`${KV_PREFIX3}.section.model`, true)));
1248
- setSectionDist(Boolean(props.api.kv.get(`${KV_PREFIX3}.section.dist`, true)));
1249
- setSectionSkills(Boolean(props.api.kv.get(`${KV_PREFIX3}.section.skills`, true)));
1250
- setSectionBalance(Boolean(props.api.kv.get(`${KV_PREFIX3}.section.balance`, true)));
1251
- const bv = props.api.kv.get(`${KV_PREFIX3}.border`, true);
1267
+ setSectionDetail(Boolean(props.api.kv.get(`${KV_PREFIX4}.section.detail`, true)));
1268
+ setSectionModel(Boolean(props.api.kv.get(`${KV_PREFIX4}.section.model`, true)));
1269
+ setSectionDist(Boolean(props.api.kv.get(`${KV_PREFIX4}.section.dist`, true)));
1270
+ setSectionSkills(Boolean(props.api.kv.get(`${KV_PREFIX4}.section.skills`, true)));
1271
+ setSectionBalance(Boolean(props.api.kv.get(`${KV_PREFIX4}.section.balance`, true)));
1272
+ const bv = props.api.kv.get(`${KV_PREFIX4}.border`, true);
1252
1273
  setBorderVisible(bv !== false);
1253
- const cachedDist = props.api.kv.get(`${KV_PREFIX3}.dist_snapshot`);
1274
+ const cachedDist = props.api.kv.get(`${KV_PREFIX4}.dist_snapshot`);
1254
1275
  if (cachedDist) {
1255
1276
  setLastDist(cachedDist);
1256
1277
  setLastHasDist(true);
@@ -2133,7 +2154,8 @@ import { effect as _$effect2 } from "@opentui/solid";
2133
2154
  import { insert as _$insert2 } from "@opentui/solid";
2134
2155
  import { createComponent as _$createComponent2 } from "@opentui/solid";
2135
2156
  import { createElement as _$createElement2 } from "@opentui/solid";
2136
- import { createMemo as createMemo2, For, Show as Show2 } from "solid-js";
2157
+ import { memo as _$memo2 } from "@opentui/solid";
2158
+ import { createMemo as createMemo2, For, onMount as onMount2, Show as Show2 } from "solid-js";
2137
2159
 
2138
2160
  // src/v2/data.ts
2139
2161
  function collectLastHitRate(context, sessionID) {
@@ -2186,8 +2208,16 @@ function mapTheme(theme) {
2186
2208
  }
2187
2209
 
2188
2210
  // src/v2/status.tsx
2211
+ var KV_PREFIX = "cache_panel";
2189
2212
  function StatusView(props) {
2190
2213
  const t = createT(() => props.signals.langCode());
2214
+ onMount2(() => {
2215
+ try {
2216
+ const v = props.api.kv.get(`${KV_PREFIX}.section.bottom`, true);
2217
+ props.signals.setSectionBottom(v !== false);
2218
+ } catch {
2219
+ }
2220
+ });
2191
2221
  const pal = createMemo2(() => {
2192
2222
  const th = mapTheme(props.context.theme);
2193
2223
  const sat = (k, fb) => desaturateTo(th[k], MAX_SAT, fb);
@@ -2267,7 +2297,7 @@ function StatusView(props) {
2267
2297
  });
2268
2298
  return _$createComponent2(Show2, {
2269
2299
  get when() {
2270
- return segsW() > 0;
2300
+ return _$memo2(() => !!(segsW() > 0 && stats().hitRate >= 0))() && props.signals.sectionBottom();
2271
2301
  },
2272
2302
  get children() {
2273
2303
  var _el$ = _$createElement2("text");
@@ -2290,7 +2320,7 @@ function StatusView(props) {
2290
2320
  }
2291
2321
 
2292
2322
  // src/v2/commands.ts
2293
- var KV_PREFIX = "cache_panel";
2323
+ var KV_PREFIX2 = "cache_panel";
2294
2324
  function extractToolParts(msg) {
2295
2325
  if (!Array.isArray(msg?.content)) return [];
2296
2326
  return msg.content.filter((p) => p?.type === "tool");
@@ -2319,13 +2349,13 @@ function currentSessionID(context) {
2319
2349
  function makeCommands(context, api, signals) {
2320
2350
  const t = () => createT(() => signals.langCode());
2321
2351
  const providerOptionTitle = (p, current) => {
2322
- const hasManual = !!api.kv.get(`${KV_PREFIX}.balance.${p.id}.key`, "");
2352
+ const hasManual = !!api.kv.get(`${KV_PREFIX2}.balance.${p.id}.key`, "");
2323
2353
  const hasAuto = !hasManual && !!findOpencodeKeyV2(context, p);
2324
2354
  const mark = hasManual ? t()("keyUser") : hasAuto ? t()("keyOpenCode") : t()("keyNotSet");
2325
2355
  return p.name + mark + (current && p.id === current ? " *" : "");
2326
2356
  };
2327
2357
  const promptBalanceKey = async (provider) => {
2328
- const current = api.kv.get(`${KV_PREFIX}.balance.${provider.id}.key`, "") ?? "";
2358
+ const current = api.kv.get(`${KV_PREFIX2}.balance.${provider.id}.key`, "") ?? "";
2329
2359
  const val = await context.ui.dialog.prompt({
2330
2360
  title: provider.name,
2331
2361
  message: t()("balKeyPrompt", { p: provider.name }),
@@ -2341,7 +2371,7 @@ function makeCommands(context, api, signals) {
2341
2371
  } else {
2342
2372
  key = input;
2343
2373
  }
2344
- await api.kv.set(`${KV_PREFIX}.balance.${provider.id}.key`, key);
2374
+ await api.kv.set(`${KV_PREFIX2}.balance.${provider.id}.key`, key);
2345
2375
  signals.setBalanceRefresh(signals.balanceRefresh() + 1);
2346
2376
  context.ui.toast.show({ message: key ? t()("keySaved") : t()("keyCleared") });
2347
2377
  };
@@ -2365,9 +2395,9 @@ function makeCommands(context, api, signals) {
2365
2395
  if (!opt) return;
2366
2396
  const sym = CURRENCIES[opt] ?? "$";
2367
2397
  const defRate = DEFAULT_RATES[opt] ?? 1;
2368
- await api.kv.set(`${KV_PREFIX}.currency`, sym);
2369
- await api.kv.set(`${KV_PREFIX}.rate`, defRate);
2370
- await api.kv.set(`${KV_PREFIX}.balance_currency`, opt);
2398
+ await api.kv.set(`${KV_PREFIX2}.currency`, sym);
2399
+ await api.kv.set(`${KV_PREFIX2}.rate`, defRate);
2400
+ await api.kv.set(`${KV_PREFIX2}.balance_currency`, opt);
2371
2401
  signals.setBalanceCurrency(opt);
2372
2402
  signals.setCurrencySymbol(sym);
2373
2403
  signals.setExchangeRate(defRate);
@@ -2391,7 +2421,7 @@ function makeCommands(context, api, signals) {
2391
2421
  if (val === void 0) return;
2392
2422
  const n = parseFloat(val);
2393
2423
  if (n > 0) {
2394
- await api.kv.set(`${KV_PREFIX}.rate`, n);
2424
+ await api.kv.set(`${KV_PREFIX2}.rate`, n);
2395
2425
  signals.setExchangeRate(n);
2396
2426
  context.ui.toast.show({ message: t()("rateSet", { r: n }) });
2397
2427
  }
@@ -2406,13 +2436,13 @@ function makeCommands(context, api, signals) {
2406
2436
  palette: true,
2407
2437
  slash: { name: "cache-section" },
2408
2438
  run: async () => {
2409
- const detailOn = Boolean(api.kv.get(`${KV_PREFIX}.section.detail`, true));
2410
- const modelOn = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true));
2411
- const distOn = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true));
2412
- const skillsOn = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true));
2413
- const balanceOn = Boolean(api.kv.get(`${KV_PREFIX}.section.balance`, true));
2414
- const bottomOn = Boolean(api.kv.get(`${KV_PREFIX}.section.bottom`, true));
2415
- const borderOn = Boolean(api.kv.get(`${KV_PREFIX}.border`, true));
2439
+ const detailOn = Boolean(api.kv.get(`${KV_PREFIX2}.section.detail`, true));
2440
+ const modelOn = Boolean(api.kv.get(`${KV_PREFIX2}.section.model`, true));
2441
+ const distOn = Boolean(api.kv.get(`${KV_PREFIX2}.section.dist`, true));
2442
+ const skillsOn = Boolean(api.kv.get(`${KV_PREFIX2}.section.skills`, true));
2443
+ const balanceOn = Boolean(api.kv.get(`${KV_PREFIX2}.section.balance`, true));
2444
+ const bottomOn = Boolean(api.kv.get(`${KV_PREFIX2}.section.bottom`, true));
2445
+ const borderOn = Boolean(api.kv.get(`${KV_PREFIX2}.border`, true));
2416
2446
  const labels = {
2417
2447
  detail: t()("secDetail"),
2418
2448
  model: t()("secModel"),
@@ -2437,12 +2467,12 @@ function makeCommands(context, api, signals) {
2437
2467
  });
2438
2468
  if (!opt) return;
2439
2469
  if (opt === "border") {
2440
- const cur = Boolean(api.kv.get(`${KV_PREFIX}.border`, true));
2441
- await api.kv.set(`${KV_PREFIX}.border`, !cur);
2470
+ const cur = Boolean(api.kv.get(`${KV_PREFIX2}.border`, true));
2471
+ await api.kv.set(`${KV_PREFIX2}.border`, !cur);
2442
2472
  signals.setBorderVisible(!cur);
2443
2473
  context.ui.toast.show({ message: !cur ? t()("borderShown") : t()("borderHidden") });
2444
2474
  } else {
2445
- const key = `${KV_PREFIX}.section.${opt}`;
2475
+ const key = `${KV_PREFIX2}.section.${opt}`;
2446
2476
  const cur = Boolean(api.kv.get(key, true));
2447
2477
  await api.kv.set(key, !cur);
2448
2478
  if (opt === "detail") signals.setSectionDetail(!cur);
@@ -2465,14 +2495,14 @@ function makeCommands(context, api, signals) {
2465
2495
  palette: true,
2466
2496
  slash: { name: "cache-config" },
2467
2497
  run: () => {
2468
- const sym = api.kv.get(`${KV_PREFIX}.currency`) ?? "$";
2469
- const rate = api.kv.get(`${KV_PREFIX}.rate`) ?? 1;
2470
- const detail = Boolean(api.kv.get(`${KV_PREFIX}.section.detail`, true));
2471
- const model = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true));
2472
- const dist = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true));
2473
- const skills = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true));
2474
- const balance = Boolean(api.kv.get(`${KV_PREFIX}.section.balance`, true));
2475
- const bottom = Boolean(api.kv.get(`${KV_PREFIX}.section.bottom`, true));
2498
+ const sym = api.kv.get(`${KV_PREFIX2}.currency`) ?? "$";
2499
+ const rate = api.kv.get(`${KV_PREFIX2}.rate`) ?? 1;
2500
+ const detail = Boolean(api.kv.get(`${KV_PREFIX2}.section.detail`, true));
2501
+ const model = Boolean(api.kv.get(`${KV_PREFIX2}.section.model`, true));
2502
+ const dist = Boolean(api.kv.get(`${KV_PREFIX2}.section.dist`, true));
2503
+ const skills = Boolean(api.kv.get(`${KV_PREFIX2}.section.skills`, true));
2504
+ const balance = Boolean(api.kv.get(`${KV_PREFIX2}.section.balance`, true));
2505
+ const bottom = Boolean(api.kv.get(`${KV_PREFIX2}.section.bottom`, true));
2476
2506
  const on = (v) => v ? "ON" : "OFF";
2477
2507
  context.ui.toast.show({
2478
2508
  title: t()("panelConfigTitle"),
@@ -2507,7 +2537,7 @@ function makeCommands(context, api, signals) {
2507
2537
  }))
2508
2538
  });
2509
2539
  if (!opt) return;
2510
- await api.kv.set(`${KV_PREFIX}.lang`, opt);
2540
+ await api.kv.set(`${KV_PREFIX2}.lang`, opt);
2511
2541
  signals.setLangCode(opt);
2512
2542
  context.ui.toast.show({ message: t()("langSwitched") });
2513
2543
  }
@@ -2537,18 +2567,18 @@ function makeCommands(context, api, signals) {
2537
2567
  if (!opt) return;
2538
2568
  if (opt === "__auto__") {
2539
2569
  const next = !auto;
2540
- await api.kv.set(`${KV_PREFIX}.balance.auto`, next);
2570
+ await api.kv.set(`${KV_PREFIX2}.balance.auto`, next);
2541
2571
  signals.setAutoBalance(next);
2542
2572
  context.ui.toast.show({ message: next ? t()("autoSwitchOn") : t()("autoSwitchOff") });
2543
2573
  } else {
2544
2574
  const provider = getBalanceProvider(opt);
2545
- await api.kv.set(`${KV_PREFIX}.balance.provider`, provider.id);
2546
- await api.kv.set(`${KV_PREFIX}.balance.auto`, false);
2575
+ await api.kv.set(`${KV_PREFIX2}.balance.provider`, provider.id);
2576
+ await api.kv.set(`${KV_PREFIX2}.balance.auto`, false);
2547
2577
  signals.setBalanceProviderId(provider.id);
2548
2578
  signals.setAutoBalance(false);
2549
2579
  signals.setBalanceUnsupported(false);
2550
2580
  signals.setBalanceRefresh(signals.balanceRefresh() + 1);
2551
- const hasKey = !!api.kv.get(`${KV_PREFIX}.balance.${provider.id}.key`, "");
2581
+ const hasKey = !!api.kv.get(`${KV_PREFIX2}.balance.${provider.id}.key`, "");
2552
2582
  if (!hasKey) {
2553
2583
  await promptBalanceKey(provider);
2554
2584
  } else {
@@ -2575,8 +2605,8 @@ function makeCommands(context, api, signals) {
2575
2605
  });
2576
2606
  if (!opt) return;
2577
2607
  const provider = getBalanceProvider(opt);
2578
- await api.kv.set(`${KV_PREFIX}.balance.provider`, provider.id);
2579
- await api.kv.set(`${KV_PREFIX}.balance.auto`, false);
2608
+ await api.kv.set(`${KV_PREFIX2}.balance.provider`, provider.id);
2609
+ await api.kv.set(`${KV_PREFIX2}.balance.auto`, false);
2580
2610
  signals.setBalanceProviderId(provider.id);
2581
2611
  signals.setAutoBalance(false);
2582
2612
  signals.setBalanceRefresh(signals.balanceRefresh() + 1);
@@ -2663,7 +2693,7 @@ function makeCommands(context, api, signals) {
2663
2693
  return true;
2664
2694
  });
2665
2695
  if (unique.length > 0) {
2666
- const currentSid = signals.overrideSessionId() ?? api.kv.get(`${KV_PREFIX}.session`, "");
2696
+ const currentSid = signals.overrideSessionId() ?? api.kv.get(`${KV_PREFIX2}.session`, "");
2667
2697
  const options = unique.map((c, i) => ({
2668
2698
  title: `${i + 1}. ${c.title}`,
2669
2699
  value: c.value,
@@ -2682,11 +2712,11 @@ function makeCommands(context, api, signals) {
2682
2712
  if (!opt) return;
2683
2713
  if (opt === backValue) {
2684
2714
  signals.setOverrideSessionId(void 0);
2685
- await api.kv.set(`${KV_PREFIX}.session`, "");
2715
+ await api.kv.set(`${KV_PREFIX2}.session`, "");
2686
2716
  context.ui.toast.show({ message: t()("backToMain") });
2687
2717
  } else {
2688
2718
  signals.setOverrideSessionId(opt);
2689
- await api.kv.set(`${KV_PREFIX}.session`, opt);
2719
+ await api.kv.set(`${KV_PREFIX2}.session`, opt);
2690
2720
  context.ui.toast.show({ message: t()("subAgentSwitched", { s: opt.slice(0, 24) + "\u2026" }) });
2691
2721
  }
2692
2722
  } else {
@@ -2699,7 +2729,7 @@ function makeCommands(context, api, signals) {
2699
2729
  const sid = val.trim();
2700
2730
  if (sid) {
2701
2731
  signals.setOverrideSessionId(sid);
2702
- await api.kv.set(`${KV_PREFIX}.session`, sid);
2732
+ await api.kv.set(`${KV_PREFIX2}.session`, sid);
2703
2733
  context.ui.toast.show({ message: t()("subAgentSwitched", { s: sid.slice(0, 24) + "\u2026" }) });
2704
2734
  }
2705
2735
  }
@@ -2715,7 +2745,7 @@ function makeCommands(context, api, signals) {
2715
2745
  slash: { name: "cache-session-back" },
2716
2746
  run: async () => {
2717
2747
  signals.setOverrideSessionId(void 0);
2718
- await api.kv.set(`${KV_PREFIX}.session`, "");
2748
+ await api.kv.set(`${KV_PREFIX2}.session`, "");
2719
2749
  context.ui.toast.show({ message: t()("backToMain") });
2720
2750
  }
2721
2751
  }
@@ -2723,7 +2753,7 @@ function makeCommands(context, api, signals) {
2723
2753
  }
2724
2754
 
2725
2755
  // src/v2/index.tsx
2726
- var KV_PREFIX2 = "cache_panel";
2756
+ var KV_PREFIX3 = "cache_panel";
2727
2757
  var BALANCE_POLL_MS = 5 * 60 * 1e3;
2728
2758
  var DEBUG_LANG = typeof process !== "undefined" ? process.env?.CACHE_TUI_LANG : void 0;
2729
2759
  var INIT_LANG = DEBUG_LANG !== void 0 && LANG_META.some((m) => m.code === DEBUG_LANG) ? DEBUG_LANG : detectLang();
@@ -2795,9 +2825,9 @@ function PluginRoot(props) {
2795
2825
  mode: "global",
2796
2826
  commands: makeCommands(props.context, props.api, props.signals)
2797
2827
  }));
2798
- onMount2(() => {
2828
+ onMount3(() => {
2799
2829
  try {
2800
- const saved = props.api.kv.get(`${KV_PREFIX2}.lang`);
2830
+ const saved = props.api.kv.get(`${KV_PREFIX3}.lang`);
2801
2831
  if (saved && LANG_META.some((m) => m.code === saved)) {
2802
2832
  props.signals.setLangCode(saved);
2803
2833
  }
@@ -2806,7 +2836,7 @@ function PluginRoot(props) {
2806
2836
  });
2807
2837
  const pollBalance = async () => {
2808
2838
  const provider = getBalanceProvider(props.signals.balanceProviderId());
2809
- const key = props.api.kv.get(`${KV_PREFIX2}.balance.${provider.id}.key`, "") || findOpencodeKeyV2(props.context, provider);
2839
+ const key = props.api.kv.get(`${KV_PREFIX3}.balance.${provider.id}.key`, "") || findOpencodeKeyV2(props.context, provider);
2810
2840
  const set = props.signals.setBalanceState;
2811
2841
  if (props.signals.balanceUnsupported()) {
2812
2842
  set({
@@ -2883,7 +2913,7 @@ function PluginRoot(props) {
2883
2913
  lastSlotSid = sid;
2884
2914
  if (props.signals.overrideSessionId()) {
2885
2915
  props.signals.setOverrideSessionId(void 0);
2886
- void props.api.kv.set(`${KV_PREFIX2}.session`, "");
2916
+ void props.api.kv.set(`${KV_PREFIX3}.session`, "");
2887
2917
  }
2888
2918
  }
2889
2919
  });
@@ -2922,6 +2952,7 @@ var mod = {
2922
2952
  append: "prompt.footer.status",
2923
2953
  render: (props) => _$createComponent3(StatusView, {
2924
2954
  context,
2955
+ api,
2925
2956
  signals,
2926
2957
  get sessionID() {
2927
2958
  return String(props.sessionID ?? "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-visual-cache",
3
- "version": "1.7.0-beta.3",
3
+ "version": "1.7.0-beta.5",
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",
package/src/_version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION="1.7.0-beta.3";
2
+ export const PLUGIN_VERSION="1.7.0-beta.5";
@@ -125,8 +125,32 @@ const moonshotProvider: BalanceProvider = {
125
125
  },
126
126
  }
127
127
 
128
+ const hyperProvider: BalanceProvider = {
129
+ id: "hyper",
130
+ name: "Charm Hyper",
131
+ keyPlaceholder: "sk-hyper-...",
132
+ async fetchBalance(apiKey, signal) {
133
+ // 官方接口:GET /v1/credits → {"balance": 98}(积分余额)
134
+ const res = await fetch("https://hyper.charm.land/v1/credits", {
135
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
136
+ signal,
137
+ })
138
+ if (!res.ok) {
139
+ if (res.status === 401) throw new BalanceError("401")
140
+ if (res.status === 403) throw new BalanceError("403")
141
+ throw new BalanceError(String(res.status))
142
+ }
143
+ const json = await res.json() as { balance?: string | number }
144
+ const balance = json.balance
145
+ if (typeof balance === "undefined" || balance === null) throw new BalanceError("EMPTY")
146
+ // hyper 积分换算:100 积分 = $5,即 1 积分 = $0.05
147
+ const usd = (Number(balance) * 0.05).toFixed(2)
148
+ return [{ currency: "USD", total: usd }]
149
+ },
150
+ }
151
+
128
152
  /** 已注册的 provider 列表(按需追加新适配器)。 */
129
- export const balanceProviders: BalanceProvider[] = [deepseekProvider, siliconflowProvider, openrouterProvider, moonshotProvider]
153
+ export const balanceProviders: BalanceProvider[] = [deepseekProvider, siliconflowProvider, openrouterProvider, moonshotProvider, hyperProvider]
130
154
 
131
155
  /** 按 id 取 provider;未知 id 回退到第一个。 */
132
156
  export function getBalanceProvider(id: string): BalanceProvider {