opencode-visual-cache 1.3.0 → 1.5.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/dist/tui.js CHANGED
@@ -13,7 +13,109 @@ import { createElement as _$createElement } from "@opentui/solid";
13
13
  import { createMemo, createSignal, createEffect, onMount, onCleanup, Show, untrack } from "solid-js";
14
14
 
15
15
  // src/_version.ts
16
- var PLUGIN_VERSION = "1.3.0";
16
+ var PLUGIN_VERSION = "1.5.0";
17
+
18
+ // src/balance-providers.ts
19
+ var BalanceError = class extends Error {
20
+ };
21
+ var siliconflowProvider = {
22
+ id: "siliconflow",
23
+ name: "SiliconFlow",
24
+ keyPlaceholder: "sk-...",
25
+ async fetchBalance(apiKey, signal) {
26
+ const res = await fetch("https://api.siliconflow.cn/v1/user/info", {
27
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
28
+ signal
29
+ });
30
+ if (!res.ok) {
31
+ if (res.status === 401) throw new BalanceError("401");
32
+ if (res.status === 403) throw new BalanceError("403");
33
+ throw new BalanceError(String(res.status));
34
+ }
35
+ const json = await res.json();
36
+ const total = json.data?.totalBalance ?? json.data?.balance;
37
+ if (typeof total === "undefined" || total === null) throw new BalanceError("EMPTY");
38
+ return [{ currency: "CNY", total: String(total) }];
39
+ }
40
+ };
41
+ var deepseekProvider = {
42
+ id: "deepseek",
43
+ name: "DeepSeek",
44
+ keyPlaceholder: "sk-...",
45
+ async fetchBalance(apiKey, signal) {
46
+ const res = await fetch("https://api.deepseek.com/user/balance", {
47
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
48
+ signal
49
+ });
50
+ if (!res.ok) {
51
+ if (res.status === 401) throw new BalanceError("401");
52
+ if (res.status === 402 || res.status === 403) throw new BalanceError("403");
53
+ throw new BalanceError(String(res.status));
54
+ }
55
+ const json = await res.json();
56
+ const infos = json.balance_infos ?? [];
57
+ if (infos.length === 0) throw new BalanceError("EMPTY");
58
+ return infos.map((info) => ({
59
+ currency: info.currency ?? "CNY",
60
+ total: info.total_balance ?? "0"
61
+ }));
62
+ }
63
+ };
64
+ var openrouterProvider = {
65
+ id: "openrouter",
66
+ name: "OpenRouter",
67
+ keyPlaceholder: "sk-or-...",
68
+ async fetchBalance(apiKey, signal) {
69
+ const res = await fetch("https://openrouter.ai/api/v1/credits", {
70
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
71
+ signal
72
+ });
73
+ if (!res.ok) {
74
+ if (res.status === 401 || res.status === 403) throw new BalanceError("403");
75
+ throw new BalanceError(String(res.status));
76
+ }
77
+ const json = await res.json();
78
+ const credits = json.data?.total_credits;
79
+ const usage = json.data?.total_usage;
80
+ if (typeof credits !== "number" || typeof usage !== "number") throw new BalanceError("EMPTY");
81
+ return [{ currency: "USD", total: (credits - usage).toFixed(2) }];
82
+ }
83
+ };
84
+ var moonshotProvider = {
85
+ id: "moonshot",
86
+ name: "Moonshot",
87
+ keyPlaceholder: "sk-...",
88
+ async fetchBalance(apiKey, signal) {
89
+ const res = await fetch("https://api.moonshot.cn/v1/users/me/balance", {
90
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
91
+ signal
92
+ });
93
+ if (!res.ok) {
94
+ if (res.status === 401) throw new BalanceError("401");
95
+ if (res.status === 403) throw new BalanceError("403");
96
+ throw new BalanceError(String(res.status));
97
+ }
98
+ const json = await res.json();
99
+ const balance = json.data?.available_balance;
100
+ if (typeof balance === "undefined" || balance === null) throw new BalanceError("EMPTY");
101
+ return [{ currency: "CNY", total: String(balance) }];
102
+ }
103
+ };
104
+ var balanceProviders = [deepseekProvider, siliconflowProvider, openrouterProvider, moonshotProvider];
105
+ function getBalanceProvider(id) {
106
+ return balanceProviders.find((p) => p.id === id) ?? balanceProviders[0] ?? deepseekProvider;
107
+ }
108
+ function matchBalanceProvider(providerId) {
109
+ const id = providerId.toLowerCase();
110
+ const exact = balanceProviders.find((p) => p.id.toLowerCase() === id);
111
+ if (exact) return exact;
112
+ return balanceProviders.find((p) => id.startsWith(p.id.toLowerCase()));
113
+ }
114
+ function maskKey(k) {
115
+ if (!k) return "";
116
+ if (k.length <= 10) return k.slice(0, 5) + "*".repeat(Math.max(3, k.length - 5));
117
+ return k.slice(0, 5) + "*".repeat(Math.max(3, k.length - 10)) + k.slice(-5);
118
+ }
17
119
 
18
120
  // src/index.tsx
19
121
  function charColumns(c) {
@@ -89,7 +191,15 @@ var ZH_T = {
89
191
  distOut: "\u8F93\u51FA:",
90
192
  secDetail: "\u660E\u7EC6",
91
193
  secModel: "\u6A21\u578B",
92
- secSkills: "\u5DF2\u52A0\u8F7D\u6280\u80FD"
194
+ secSkills: "\u5DF2\u52A0\u8F7D\u6280\u80FD",
195
+ balTotal: "\u603B\u4F59\u989D:",
196
+ balNoKey: "\u672A\u914D\u7F6E {p} API Key",
197
+ balLoading: "\u67E5\u8BE2\u4E2D...",
198
+ balError: "\u67E5\u8BE2\u5931\u8D25",
199
+ balErr401: "API Key \u65E0\u6548",
200
+ balErr403: "\u4F59\u989D\u67E5\u8BE2\u88AB\u62D2\u7EDD",
201
+ balErrEmpty: "\u672A\u83B7\u53D6\u5230\u4F59\u989D\u6570\u636E",
202
+ balErrTimeout: "\u67E5\u8BE2\u8D85\u65F6"
93
203
  };
94
204
  var EN_T = {
95
205
  title: "Token Cache",
@@ -120,7 +230,15 @@ var EN_T = {
120
230
  distOut: "Output:",
121
231
  secDetail: "Detail",
122
232
  secModel: "Model",
123
- secSkills: "Loaded Skills"
233
+ secSkills: "Loaded Skills",
234
+ balTotal: "Total:",
235
+ balNoKey: "{p} API Key not set",
236
+ balLoading: "Fetching...",
237
+ balError: "Fetch failed",
238
+ balErr401: "Invalid API Key",
239
+ balErr403: "Balance request rejected",
240
+ balErrEmpty: "No balance data",
241
+ balErrTimeout: "Request timed out"
124
242
  };
125
243
  function rgb(raw) {
126
244
  if (typeof raw === "string" && raw.startsWith("#")) {
@@ -232,6 +350,30 @@ function estimateTokens(text) {
232
350
  const asciiPerToken = jsonLike ? 3.5 : codeLike ? 3.5 : 4;
233
351
  return Math.max(1, Math.ceil(ascii / asciiPerToken + cjk / 1));
234
352
  }
353
+ var BALANCE_POLL_MS = 5 * 60 * 1e3;
354
+ function convertBalance(target, targetRate, amount, from) {
355
+ if (from === target) return amount;
356
+ const fromRate = DEFAULT_RATES[from] ?? 1;
357
+ const usd = from === "USD" ? amount : amount / fromRate;
358
+ return target === "USD" ? usd : usd * targetRate;
359
+ }
360
+ function findOpencodeKey(api, provider) {
361
+ try {
362
+ const provs = api.state.provider;
363
+ const id = provider.id.toLowerCase();
364
+ const hit = provs.find((p) => p.id.toLowerCase() === id) ?? provs.find((p) => p.id.toLowerCase().startsWith(id));
365
+ if (!hit) return "";
366
+ const k = typeof hit.key === "string" ? hit.key : "";
367
+ if (k) return k;
368
+ return typeof hit.options?.apiKey === "string" ? hit.options.apiKey : "";
369
+ } catch {
370
+ return "";
371
+ }
372
+ }
373
+ function balanceSymbol(currency) {
374
+ const sym = CURRENCIES[currency];
375
+ return sym ?? currency + " ";
376
+ }
235
377
  var CURRENCIES = {
236
378
  USD: "$",
237
379
  CNY: "\xA5",
@@ -279,6 +421,15 @@ function TokenCachePanel(props) {
279
421
  setSectionDist,
280
422
  sectionSkills,
281
423
  setSectionSkills,
424
+ sectionBalance,
425
+ setSectionBalance,
426
+ balanceRefresh,
427
+ balanceProviderId,
428
+ setBalanceProviderId,
429
+ autoBalance,
430
+ setAutoBalance,
431
+ balanceCurrency,
432
+ setBalanceCurrency,
282
433
  borderVisible,
283
434
  setBorderVisible
284
435
  } = props.signals;
@@ -329,6 +480,98 @@ function TokenCachePanel(props) {
329
480
  hasSkills: false
330
481
  });
331
482
  const [refreshTick, setRefreshTick] = createSignal(0);
483
+ const [balanceState, setBalanceState] = createSignal({
484
+ status: "idle",
485
+ data: null,
486
+ lastFetch: 0
487
+ });
488
+ let balanceSeq = 0;
489
+ const providerName = createMemo(() => getBalanceProvider(balanceProviderId()).name);
490
+ const pollBalance = async () => {
491
+ const provider = getBalanceProvider(balanceProviderId());
492
+ const key = props.api.kv.get(`${KV_PREFIX}.balance.${provider.id}.key`, "") || findOpencodeKey(props.api, provider);
493
+ if (!key) {
494
+ setBalanceState({
495
+ status: "idle",
496
+ data: null,
497
+ lastFetch: 0,
498
+ error: void 0,
499
+ key: void 0
500
+ });
501
+ return;
502
+ }
503
+ const now = Date.now();
504
+ const prev = balanceState();
505
+ if (prev.status === "ok" && prev.key === key && now - prev.lastFetch < BALANCE_POLL_MS) return;
506
+ const seq = ++balanceSeq;
507
+ setBalanceState({
508
+ ...prev,
509
+ status: "loading",
510
+ error: void 0,
511
+ key
512
+ });
513
+ const controller = new AbortController();
514
+ let timedOut = false;
515
+ const timer = setTimeout(() => {
516
+ timedOut = true;
517
+ controller.abort();
518
+ }, 1e4);
519
+ try {
520
+ const data2 = await provider.fetchBalance(key, controller.signal);
521
+ clearTimeout(timer);
522
+ if (seq !== balanceSeq) return;
523
+ setBalanceState({
524
+ status: "ok",
525
+ data: data2,
526
+ lastFetch: Date.now(),
527
+ error: void 0,
528
+ key
529
+ });
530
+ } catch (err) {
531
+ clearTimeout(timer);
532
+ if (seq !== balanceSeq) return;
533
+ const code = timedOut ? "TIMEOUT" : err instanceof Error ? err.message : "";
534
+ setBalanceState({
535
+ status: "error",
536
+ data: null,
537
+ lastFetch: 0,
538
+ error: code,
539
+ key
540
+ });
541
+ }
542
+ };
543
+ createEffect(() => {
544
+ void balanceRefresh();
545
+ untrack(() => {
546
+ void pollBalance();
547
+ });
548
+ });
549
+ createEffect(() => {
550
+ if (!autoBalance()) return;
551
+ const sid = props.signals.overrideSessionId() ?? props.sessionId;
552
+ const msgs = props.api.state.session.messages(sid);
553
+ let pid = "";
554
+ for (let i = msgs.length - 1; i >= 0; i--) {
555
+ const m = msgs[i];
556
+ if (m.role === "assistant" && m.providerID) {
557
+ pid = m.providerID;
558
+ break;
559
+ }
560
+ }
561
+ if (!pid) {
562
+ try {
563
+ const session = props.api.state.session.get(sid);
564
+ pid = session?.model?.providerID ?? "";
565
+ } catch {
566
+ }
567
+ }
568
+ if (!pid) return;
569
+ const hit = matchBalanceProvider(pid);
570
+ if (hit && hit.id !== balanceProviderId()) {
571
+ setBalanceProviderId(hit.id);
572
+ props.signals.setBalanceRefresh(props.signals.balanceRefresh() + 1);
573
+ }
574
+ });
332
575
  let lastMainSid = props.sessionId;
333
576
  createEffect(() => {
334
577
  const sid = props.sessionId;
@@ -395,7 +638,7 @@ function TokenCachePanel(props) {
395
638
  const freshTotal = input + read, sessionHitRate = freshTotal > 0 ? read / freshTotal * 100 : 0;
396
639
  const model = mid.split("/").pop() ?? mid, hasPricing = inputRate > 0 || cacheReadRate > 0 || cacheWriteRate > 0;
397
640
  const hasTrendData = prevMsgHitRate >= 0 && lastMsgHitRate >= 0;
398
- const trend = hasTrendData ? lastMsgHitRate - prevMsgHitRate : 0, providerName = pid || "";
641
+ const trend = hasTrendData ? lastMsgHitRate - prevMsgHitRate : 0, providerName2 = pid || "";
399
642
  const distData = untrack(() => {
400
643
  let dist = {
401
644
  system: 0,
@@ -520,7 +763,7 @@ function TokenCachePanel(props) {
520
763
  hasData: read > 0 || write > 0 || input > 0 || output > 0 || cost > 0,
521
764
  trend,
522
765
  hasTrendData,
523
- providerName,
766
+ providerName: providerName2,
524
767
  sessionHitRate,
525
768
  dist: distData.finalDist,
526
769
  hasDistData: distData.finalHasDist,
@@ -570,10 +813,26 @@ function TokenCachePanel(props) {
570
813
  const rate = props.api.kv.get(`${KV_PREFIX}.rate`);
571
814
  if (typeof sym === "string") setCurrencySymbol(sym);
572
815
  if (typeof rate === "number" && rate > 0) setExchangeRate(rate);
816
+ const balCur = props.api.kv.get(`${KV_PREFIX}.balance_currency`);
817
+ if (typeof balCur === "string") setBalanceCurrency(balCur);
818
+ const savedProvider = props.api.kv.get(`${KV_PREFIX}.balance.provider`);
819
+ if (typeof savedProvider === "string" && balanceProviders.some((p) => p.id === savedProvider)) {
820
+ setBalanceProviderId(savedProvider);
821
+ }
822
+ const savedAuto = props.api.kv.get(`${KV_PREFIX}.balance.auto`);
823
+ if (typeof savedAuto === "boolean") setAutoBalance(savedAuto);
824
+ const legacyKey = props.api.kv.get(`${KV_PREFIX}.ds_key`, "");
825
+ if (legacyKey) {
826
+ const dsKey = props.api.kv.get(`${KV_PREFIX}.balance.deepseek.key`, "");
827
+ if (!dsKey) props.api.kv.set(`${KV_PREFIX}.balance.deepseek.key`, legacyKey);
828
+ props.api.kv.set(`${KV_PREFIX}.ds_key`, "");
829
+ }
830
+ props.signals.setBalanceRefresh(props.signals.balanceRefresh() + 1);
573
831
  setSectionDetail(Boolean(props.api.kv.get(`${KV_PREFIX}.section.detail`, true)));
574
832
  setSectionModel(Boolean(props.api.kv.get(`${KV_PREFIX}.section.model`, true)));
575
833
  setSectionDist(Boolean(props.api.kv.get(`${KV_PREFIX}.section.dist`, true)));
576
834
  setSectionSkills(Boolean(props.api.kv.get(`${KV_PREFIX}.section.skills`, true)));
835
+ setSectionBalance(Boolean(props.api.kv.get(`${KV_PREFIX}.section.balance`, true)));
577
836
  const bv = props.api.kv.get(`${KV_PREFIX}.border`, true);
578
837
  setBorderVisible(bv !== false);
579
838
  const savedLang = props.api.kv.get(`${KV_PREFIX}.lang`);
@@ -626,8 +885,10 @@ function TokenCachePanel(props) {
626
885
  setRefreshTick((v) => v + 1);
627
886
  });
628
887
  setRefreshTick((v) => v + 1);
888
+ const balanceTimer = setInterval(pollBalance, BALANCE_POLL_MS);
629
889
  onCleanup(() => {
630
890
  clearTimeout(partTimer);
891
+ clearInterval(balanceTimer);
631
892
  unsubPart();
632
893
  unsubMsg();
633
894
  unsubSession();
@@ -798,25 +1059,25 @@ function TokenCachePanel(props) {
798
1059
  const prefix = " \u21B3 " + (langZH() ? "\u5B50\u4EE3\u7406: " : "Sub: ");
799
1060
  const maxSidW = Math.max(6, panelWidth() - visualWidth(prefix));
800
1061
  return (() => {
801
- var _el$51 = _$createElement("text"), _el$52 = _$createElement("span"), _el$53 = _$createElement("span");
802
- _$insertNode(_el$51, _el$52);
803
- _$insertNode(_el$51, _el$53);
804
- _$insert(_el$52, prefix);
805
- _$insert(_el$53, () => truncateVisual(props.signals.overrideSessionId(), maxSidW));
1062
+ var _el$64 = _$createElement("text"), _el$65 = _$createElement("span"), _el$66 = _$createElement("span");
1063
+ _$insertNode(_el$64, _el$65);
1064
+ _$insertNode(_el$64, _el$66);
1065
+ _$insert(_el$65, prefix);
1066
+ _$insert(_el$66, () => truncateVisual(props.signals.overrideSessionId(), maxSidW));
806
1067
  _$effect((_p$) => {
807
- var _v$1 = {
1068
+ var _v$15 = {
808
1069
  fg: pal().muted
809
- }, _v$10 = {
1070
+ }, _v$16 = {
810
1071
  fg: pal().text
811
1072
  };
812
- _v$1 !== _p$.e && (_p$.e = _$setProp(_el$52, "style", _v$1, _p$.e));
813
- _v$10 !== _p$.t && (_p$.t = _$setProp(_el$53, "style", _v$10, _p$.t));
1073
+ _v$15 !== _p$.e && (_p$.e = _$setProp(_el$65, "style", _v$15, _p$.e));
1074
+ _v$16 !== _p$.t && (_p$.t = _$setProp(_el$66, "style", _v$16, _p$.t));
814
1075
  return _p$;
815
1076
  }, {
816
1077
  e: void 0,
817
1078
  t: void 0
818
1079
  });
819
- return _el$51;
1080
+ return _el$64;
820
1081
  })();
821
1082
  })();
822
1083
  }
@@ -826,30 +1087,30 @@ function TokenCachePanel(props) {
826
1087
  },
827
1088
  get fallback() {
828
1089
  return [(() => {
829
- var _el$54 = _$createElement("text");
830
- _$insert(_el$54, sep);
831
- _$effect((_$p) => _$setProp(_el$54, "fg", pal().muted, _$p));
832
- return _el$54;
1090
+ var _el$67 = _$createElement("text");
1091
+ _$insert(_el$67, sep);
1092
+ _$effect((_$p) => _$setProp(_el$67, "fg", pal().muted, _$p));
1093
+ return _el$67;
833
1094
  })(), (() => {
834
- var _el$55 = _$createElement("text"), _el$56 = _$createElement("span"), _el$58 = _$createElement("span");
835
- _$insertNode(_el$55, _el$56);
836
- _$insertNode(_el$55, _el$58);
837
- _$insertNode(_el$56, _$createTextNode(`> `));
838
- _$insert(_el$58, () => t().noData);
1095
+ var _el$68 = _$createElement("text"), _el$69 = _$createElement("span"), _el$71 = _$createElement("span");
1096
+ _$insertNode(_el$68, _el$69);
1097
+ _$insertNode(_el$68, _el$71);
1098
+ _$insertNode(_el$69, _$createTextNode(`> `));
1099
+ _$insert(_el$71, () => t().noData);
839
1100
  _$effect((_p$) => {
840
- var _v$11 = {
1101
+ var _v$17 = {
841
1102
  fg: pal().muted
842
- }, _v$12 = {
1103
+ }, _v$18 = {
843
1104
  fg: pal().muted
844
1105
  };
845
- _v$11 !== _p$.e && (_p$.e = _$setProp(_el$56, "style", _v$11, _p$.e));
846
- _v$12 !== _p$.t && (_p$.t = _$setProp(_el$58, "style", _v$12, _p$.t));
1106
+ _v$17 !== _p$.e && (_p$.e = _$setProp(_el$69, "style", _v$17, _p$.e));
1107
+ _v$18 !== _p$.t && (_p$.t = _$setProp(_el$71, "style", _v$18, _p$.t));
847
1108
  return _p$;
848
1109
  }, {
849
1110
  e: void 0,
850
1111
  t: void 0
851
1112
  });
852
- return _el$55;
1113
+ return _el$68;
853
1114
  })()];
854
1115
  },
855
1116
  get children() {
@@ -1016,37 +1277,37 @@ function TokenCachePanel(props) {
1016
1277
  },
1017
1278
  get children() {
1018
1279
  return [_$memo(() => (() => {
1019
- var _el$59 = _$createElement("text"), _el$60 = _$createElement("span"), _el$61 = _$createElement("span"), _el$62 = _$createElement("b"), _el$63 = _$createElement("span");
1020
- _$insertNode(_el$59, _el$60);
1021
- _$insertNode(_el$59, _el$61);
1022
- _$insertNode(_el$59, _el$63);
1023
- _$setProp(_el$59, "onMouseUp", () => setModelOpen((o) => {
1280
+ var _el$72 = _$createElement("text"), _el$73 = _$createElement("span"), _el$74 = _$createElement("span"), _el$75 = _$createElement("b"), _el$76 = _$createElement("span");
1281
+ _$insertNode(_el$72, _el$73);
1282
+ _$insertNode(_el$72, _el$74);
1283
+ _$insertNode(_el$72, _el$76);
1284
+ _$setProp(_el$72, "onMouseUp", () => setModelOpen((o) => {
1024
1285
  const n = !o;
1025
1286
  persistFold("model", n);
1026
1287
  return n;
1027
1288
  }));
1028
- _$insert(_el$60, () => modelOpen() ? "\u25BC " : "\u25B6 ");
1029
- _$insertNode(_el$61, _el$62);
1030
- _$insert(_el$62, () => t().secModel);
1031
- _$insert(_el$63, () => sep().slice(visualWidth((modelOpen() ? "\u25BC " : "\u25B6 ") + t().secModel)));
1289
+ _$insert(_el$73, () => modelOpen() ? "\u25BC " : "\u25B6 ");
1290
+ _$insertNode(_el$74, _el$75);
1291
+ _$insert(_el$75, () => t().secModel);
1292
+ _$insert(_el$76, () => sep().slice(visualWidth((modelOpen() ? "\u25BC " : "\u25B6 ") + t().secModel)));
1032
1293
  _$effect((_p$) => {
1033
- var _v$13 = {
1294
+ var _v$19 = {
1034
1295
  fg: pal().muted
1035
- }, _v$14 = {
1296
+ }, _v$20 = {
1036
1297
  fg: pal().primary
1037
- }, _v$15 = {
1298
+ }, _v$21 = {
1038
1299
  fg: pal().muted
1039
1300
  };
1040
- _v$13 !== _p$.e && (_p$.e = _$setProp(_el$60, "style", _v$13, _p$.e));
1041
- _v$14 !== _p$.t && (_p$.t = _$setProp(_el$61, "style", _v$14, _p$.t));
1042
- _v$15 !== _p$.a && (_p$.a = _$setProp(_el$63, "style", _v$15, _p$.a));
1301
+ _v$19 !== _p$.e && (_p$.e = _$setProp(_el$73, "style", _v$19, _p$.e));
1302
+ _v$20 !== _p$.t && (_p$.t = _$setProp(_el$74, "style", _v$20, _p$.t));
1303
+ _v$21 !== _p$.a && (_p$.a = _$setProp(_el$76, "style", _v$21, _p$.a));
1043
1304
  return _p$;
1044
1305
  }, {
1045
1306
  e: void 0,
1046
1307
  t: void 0,
1047
1308
  a: void 0
1048
1309
  });
1049
- return _el$59;
1310
+ return _el$72;
1050
1311
  })()), _$createComponent(Show, {
1051
1312
  get when() {
1052
1313
  return modelOpen();
@@ -1119,37 +1380,37 @@ function TokenCachePanel(props) {
1119
1380
  },
1120
1381
  get children() {
1121
1382
  return [_$memo(() => (() => {
1122
- var _el$64 = _$createElement("text"), _el$65 = _$createElement("span"), _el$66 = _$createElement("span"), _el$67 = _$createElement("b"), _el$68 = _$createElement("span");
1123
- _$insertNode(_el$64, _el$65);
1124
- _$insertNode(_el$64, _el$66);
1125
- _$insertNode(_el$64, _el$68);
1126
- _$setProp(_el$64, "onMouseUp", () => setDistOpen((o) => {
1383
+ var _el$77 = _$createElement("text"), _el$78 = _$createElement("span"), _el$79 = _$createElement("span"), _el$80 = _$createElement("b"), _el$81 = _$createElement("span");
1384
+ _$insertNode(_el$77, _el$78);
1385
+ _$insertNode(_el$77, _el$79);
1386
+ _$insertNode(_el$77, _el$81);
1387
+ _$setProp(_el$77, "onMouseUp", () => setDistOpen((o) => {
1127
1388
  const n = !o;
1128
1389
  persistFold("dist", n);
1129
1390
  return n;
1130
1391
  }));
1131
- _$insert(_el$65, () => distOpen() ? "\u25BC " : "\u25B6 ");
1132
- _$insertNode(_el$66, _el$67);
1133
- _$insert(_el$67, () => t().distTitle);
1134
- _$insert(_el$68, () => sep().slice(visualWidth((distOpen() ? "\u25BC " : "\u25B6 ") + t().distTitle)));
1392
+ _$insert(_el$78, () => distOpen() ? "\u25BC " : "\u25B6 ");
1393
+ _$insertNode(_el$79, _el$80);
1394
+ _$insert(_el$80, () => t().distTitle);
1395
+ _$insert(_el$81, () => sep().slice(visualWidth((distOpen() ? "\u25BC " : "\u25B6 ") + t().distTitle)));
1135
1396
  _$effect((_p$) => {
1136
- var _v$16 = {
1397
+ var _v$22 = {
1137
1398
  fg: pal().muted
1138
- }, _v$17 = {
1399
+ }, _v$23 = {
1139
1400
  fg: pal().primary
1140
- }, _v$18 = {
1401
+ }, _v$24 = {
1141
1402
  fg: pal().muted
1142
1403
  };
1143
- _v$16 !== _p$.e && (_p$.e = _$setProp(_el$65, "style", _v$16, _p$.e));
1144
- _v$17 !== _p$.t && (_p$.t = _$setProp(_el$66, "style", _v$17, _p$.t));
1145
- _v$18 !== _p$.a && (_p$.a = _$setProp(_el$68, "style", _v$18, _p$.a));
1404
+ _v$22 !== _p$.e && (_p$.e = _$setProp(_el$78, "style", _v$22, _p$.e));
1405
+ _v$23 !== _p$.t && (_p$.t = _$setProp(_el$79, "style", _v$23, _p$.t));
1406
+ _v$24 !== _p$.a && (_p$.a = _$setProp(_el$81, "style", _v$24, _p$.a));
1146
1407
  return _p$;
1147
1408
  }, {
1148
1409
  e: void 0,
1149
1410
  t: void 0,
1150
1411
  a: void 0
1151
1412
  });
1152
- return _el$64;
1413
+ return _el$77;
1153
1414
  })()), _$createComponent(Show, {
1154
1415
  get when() {
1155
1416
  return distOpen();
@@ -1227,37 +1488,37 @@ function TokenCachePanel(props) {
1227
1488
  },
1228
1489
  get children() {
1229
1490
  return [_$memo(() => (() => {
1230
- var _el$69 = _$createElement("text"), _el$70 = _$createElement("span"), _el$71 = _$createElement("span"), _el$72 = _$createElement("b"), _el$73 = _$createElement("span"), _el$74 = _$createTextNode(` (`), _el$75 = _$createTextNode(`)`), _el$76 = _$createElement("span");
1231
- _$insertNode(_el$69, _el$70);
1232
- _$insertNode(_el$69, _el$71);
1233
- _$insertNode(_el$69, _el$73);
1234
- _$insertNode(_el$69, _el$76);
1235
- _$setProp(_el$69, "onMouseUp", () => setSkillsOpen((o) => {
1491
+ var _el$82 = _$createElement("text"), _el$83 = _$createElement("span"), _el$84 = _$createElement("span"), _el$85 = _$createElement("b"), _el$86 = _$createElement("span"), _el$87 = _$createTextNode(` (`), _el$88 = _$createTextNode(`)`), _el$89 = _$createElement("span");
1492
+ _$insertNode(_el$82, _el$83);
1493
+ _$insertNode(_el$82, _el$84);
1494
+ _$insertNode(_el$82, _el$86);
1495
+ _$insertNode(_el$82, _el$89);
1496
+ _$setProp(_el$82, "onMouseUp", () => setSkillsOpen((o) => {
1236
1497
  const n = !o;
1237
1498
  persistFold("skills", n);
1238
1499
  return n;
1239
1500
  }));
1240
- _$insert(_el$70, () => skillsOpen() ? "\u25BC " : "\u25B6 ");
1241
- _$insertNode(_el$71, _el$72);
1242
- _$insert(_el$72, () => t().secSkills);
1243
- _$insertNode(_el$73, _el$74);
1244
- _$insertNode(_el$73, _el$75);
1245
- _$insert(_el$73, () => data().skills.length, _el$75);
1246
- _$insert(_el$76, () => sep().slice(visualWidth((skillsOpen() ? "\u25BC " : "\u25B6 ") + t().secSkills + ` (${data().skills.length})`)));
1501
+ _$insert(_el$83, () => skillsOpen() ? "\u25BC " : "\u25B6 ");
1502
+ _$insertNode(_el$84, _el$85);
1503
+ _$insert(_el$85, () => t().secSkills);
1504
+ _$insertNode(_el$86, _el$87);
1505
+ _$insertNode(_el$86, _el$88);
1506
+ _$insert(_el$86, () => data().skills.length, _el$88);
1507
+ _$insert(_el$89, () => sep().slice(visualWidth((skillsOpen() ? "\u25BC " : "\u25B6 ") + t().secSkills + ` (${data().skills.length})`)));
1247
1508
  _$effect((_p$) => {
1248
- var _v$19 = {
1509
+ var _v$25 = {
1249
1510
  fg: pal().muted
1250
- }, _v$20 = {
1511
+ }, _v$26 = {
1251
1512
  fg: pal().primary
1252
- }, _v$21 = {
1513
+ }, _v$27 = {
1253
1514
  fg: pal().muted
1254
- }, _v$22 = {
1515
+ }, _v$28 = {
1255
1516
  fg: pal().muted
1256
1517
  };
1257
- _v$19 !== _p$.e && (_p$.e = _$setProp(_el$70, "style", _v$19, _p$.e));
1258
- _v$20 !== _p$.t && (_p$.t = _$setProp(_el$71, "style", _v$20, _p$.t));
1259
- _v$21 !== _p$.a && (_p$.a = _$setProp(_el$73, "style", _v$21, _p$.a));
1260
- _v$22 !== _p$.o && (_p$.o = _$setProp(_el$76, "style", _v$22, _p$.o));
1518
+ _v$25 !== _p$.e && (_p$.e = _$setProp(_el$83, "style", _v$25, _p$.e));
1519
+ _v$26 !== _p$.t && (_p$.t = _$setProp(_el$84, "style", _v$26, _p$.t));
1520
+ _v$27 !== _p$.a && (_p$.a = _$setProp(_el$86, "style", _v$27, _p$.a));
1521
+ _v$28 !== _p$.o && (_p$.o = _$setProp(_el$89, "style", _v$28, _p$.o));
1261
1522
  return _p$;
1262
1523
  }, {
1263
1524
  e: void 0,
@@ -1265,7 +1526,7 @@ function TokenCachePanel(props) {
1265
1526
  a: void 0,
1266
1527
  o: void 0
1267
1528
  });
1268
- return _el$69;
1529
+ return _el$82;
1269
1530
  })()), _$createComponent(Show, {
1270
1531
  get when() {
1271
1532
  return skillsOpen();
@@ -1276,10 +1537,10 @@ function TokenCachePanel(props) {
1276
1537
  const maxLabel = Math.max(4, panelWidth() - gutter() - rightW - 1);
1277
1538
  const label = truncateVisual(sk.name, maxLabel);
1278
1539
  return (() => {
1279
- var _el$77 = _$createElement("text");
1280
- _$insert(_el$77, () => justify(label, fmt(sk.tokens), t().tok));
1281
- _$effect((_$p) => _$setProp(_el$77, "fg", pal().muted, _$p));
1282
- return _el$77;
1540
+ var _el$90 = _$createElement("text");
1541
+ _$insert(_el$90, () => justify(label, fmt(sk.tokens), t().tok));
1542
+ _$effect((_$p) => _$setProp(_el$90, "fg", pal().muted, _$p));
1543
+ return _el$90;
1283
1544
  })();
1284
1545
  });
1285
1546
  }
@@ -1287,19 +1548,138 @@ function TokenCachePanel(props) {
1287
1548
  }
1288
1549
  });
1289
1550
  }
1551
+ }), _$createComponent(Show, {
1552
+ get when() {
1553
+ return sectionBalance();
1554
+ },
1555
+ get children() {
1556
+ return [(() => {
1557
+ var _el$51 = _$createElement("text");
1558
+ _$insert(_el$51, sep);
1559
+ _$effect((_$p) => _$setProp(_el$51, "fg", pal().muted, _$p));
1560
+ return _el$51;
1561
+ })(), _$createComponent(Show, {
1562
+ get when() {
1563
+ return balanceState().status === "idle";
1564
+ },
1565
+ get children() {
1566
+ var _el$52 = _$createElement("text"), _el$53 = _$createElement("span"), _el$55 = _$createElement("span");
1567
+ _$insertNode(_el$52, _el$53);
1568
+ _$insertNode(_el$52, _el$55);
1569
+ _$insertNode(_el$53, _$createTextNode(`> `));
1570
+ _$insert(_el$55, () => t().balNoKey.replace("{p}", providerName()));
1571
+ _$effect((_p$) => {
1572
+ var _v$9 = pal().muted, _v$0 = {
1573
+ fg: pal().muted
1574
+ };
1575
+ _v$9 !== _p$.e && (_p$.e = _$setProp(_el$52, "fg", _v$9, _p$.e));
1576
+ _v$0 !== _p$.t && (_p$.t = _$setProp(_el$53, "style", _v$0, _p$.t));
1577
+ return _p$;
1578
+ }, {
1579
+ e: void 0,
1580
+ t: void 0
1581
+ });
1582
+ return _el$52;
1583
+ }
1584
+ }), _$createComponent(Show, {
1585
+ get when() {
1586
+ return balanceState().status === "loading";
1587
+ },
1588
+ get children() {
1589
+ var _el$56 = _$createElement("text"), _el$57 = _$createElement("span"), _el$59 = _$createElement("span");
1590
+ _$insertNode(_el$56, _el$57);
1591
+ _$insertNode(_el$56, _el$59);
1592
+ _$insertNode(_el$57, _$createTextNode(`> `));
1593
+ _$insert(_el$59, () => t().balLoading);
1594
+ _$effect((_p$) => {
1595
+ var _v$1 = pal().muted, _v$10 = {
1596
+ fg: pal().muted
1597
+ };
1598
+ _v$1 !== _p$.e && (_p$.e = _$setProp(_el$56, "fg", _v$1, _p$.e));
1599
+ _v$10 !== _p$.t && (_p$.t = _$setProp(_el$57, "style", _v$10, _p$.t));
1600
+ return _p$;
1601
+ }, {
1602
+ e: void 0,
1603
+ t: void 0
1604
+ });
1605
+ return _el$56;
1606
+ }
1607
+ }), _$createComponent(Show, {
1608
+ get when() {
1609
+ return balanceState().status === "error";
1610
+ },
1611
+ get children() {
1612
+ var _el$60 = _$createElement("text"), _el$61 = _$createElement("span"), _el$63 = _$createElement("span");
1613
+ _$insertNode(_el$60, _el$61);
1614
+ _$insertNode(_el$60, _el$63);
1615
+ _$insertNode(_el$61, _$createTextNode(`> `));
1616
+ _$insert(_el$63, () => {
1617
+ const code = balanceState().error;
1618
+ if (code === "401") return t().balErr401;
1619
+ if (code === "403") return t().balErr403;
1620
+ if (code === "EMPTY") return t().balErrEmpty;
1621
+ if (code === "TIMEOUT") return t().balErrTimeout;
1622
+ return t().balError + (code ? ` (${code})` : "");
1623
+ });
1624
+ _$effect((_p$) => {
1625
+ var _v$11 = pal().error, _v$12 = {
1626
+ fg: pal().muted
1627
+ };
1628
+ _v$11 !== _p$.e && (_p$.e = _$setProp(_el$60, "fg", _v$11, _p$.e));
1629
+ _v$12 !== _p$.t && (_p$.t = _$setProp(_el$61, "style", _v$12, _p$.t));
1630
+ return _p$;
1631
+ }, {
1632
+ e: void 0,
1633
+ t: void 0
1634
+ });
1635
+ return _el$60;
1636
+ }
1637
+ }), _$createComponent(Show, {
1638
+ get when() {
1639
+ return _$memo(() => balanceState().status === "ok")() && balanceState().data;
1640
+ },
1641
+ get children() {
1642
+ return (() => {
1643
+ const list = balanceState().data;
1644
+ const pref = balanceCurrency();
1645
+ const native = pref ? list.find((x) => x.currency === pref) : void 0;
1646
+ if (native) {
1647
+ return (() => {
1648
+ var _el$91 = _$createElement("text");
1649
+ _$insert(_el$91, () => justify(t().balTotal, balanceSymbol(native.currency) + native.total));
1650
+ _$effect((_$p) => _$setProp(_el$91, "fg", pal().text, _$p));
1651
+ return _el$91;
1652
+ })();
1653
+ }
1654
+ const base = list[0];
1655
+ const baseAmt = parseFloat(base.total);
1656
+ const converted = Number.isFinite(baseAmt) ? convertBalance(pref || base.currency, exchangeRate(), baseAmt, base.currency) : baseAmt;
1657
+ const shown = pref && base.currency !== pref ? converted.toLocaleString("en-US", {
1658
+ maximumFractionDigits: 2
1659
+ }) : base.total;
1660
+ return (() => {
1661
+ var _el$92 = _$createElement("text");
1662
+ _$insert(_el$92, () => justify(t().balTotal, balanceSymbol(pref || base.currency) + shown));
1663
+ _$effect((_$p) => _$setProp(_el$92, "fg", pal().text, _$p));
1664
+ return _el$92;
1665
+ })();
1666
+ })();
1667
+ }
1668
+ })];
1669
+ }
1290
1670
  })];
1291
1671
  }
1292
1672
  })];
1293
1673
  }
1294
1674
  }), null);
1295
1675
  _$effect((_p$) => {
1296
- var _v$9 = {
1676
+ var _v$13 = {
1297
1677
  fg: pal().muted
1298
- }, _v$0 = {
1678
+ }, _v$14 = {
1299
1679
  fg: pal().primary
1300
1680
  };
1301
- _v$9 !== _p$.e && (_p$.e = _$setProp(_el$3, "style", _v$9, _p$.e));
1302
- _v$0 !== _p$.t && (_p$.t = _$setProp(_el$4, "style", _v$0, _p$.t));
1681
+ _v$13 !== _p$.e && (_p$.e = _$setProp(_el$3, "style", _v$13, _p$.e));
1682
+ _v$14 !== _p$.t && (_p$.t = _$setProp(_el$4, "style", _v$14, _p$.t));
1303
1683
  return _p$;
1304
1684
  }, {
1305
1685
  e: void 0,
@@ -1342,6 +1722,11 @@ var tui = async (api) => {
1342
1722
  const [sectionModel, setSectionModel] = createSignal(true);
1343
1723
  const [sectionDist, setSectionDist] = createSignal(true);
1344
1724
  const [sectionSkills, setSectionSkills] = createSignal(true);
1725
+ const [sectionBalance, setSectionBalance] = createSignal(true);
1726
+ const [balanceRefresh, setBalanceRefresh] = createSignal(0);
1727
+ const [balanceProviderId, setBalanceProviderId] = createSignal("deepseek");
1728
+ const [autoBalance, setAutoBalance] = createSignal(true);
1729
+ const [balanceCurrency, setBalanceCurrency] = createSignal("");
1345
1730
  const [borderVisible, setBorderVisible] = createSignal(true);
1346
1731
  const [langZH, setLangZH] = createSignal(LANG_ZH);
1347
1732
  const [overrideSessionId, setOverrideSessionId] = createSignal(void 0);
@@ -1360,6 +1745,16 @@ var tui = async (api) => {
1360
1745
  setSectionDist,
1361
1746
  sectionSkills,
1362
1747
  setSectionSkills,
1748
+ sectionBalance,
1749
+ setSectionBalance,
1750
+ balanceRefresh,
1751
+ setBalanceRefresh,
1752
+ balanceProviderId,
1753
+ setBalanceProviderId,
1754
+ autoBalance,
1755
+ setAutoBalance,
1756
+ balanceCurrency,
1757
+ setBalanceCurrency,
1363
1758
  borderVisible,
1364
1759
  setBorderVisible,
1365
1760
  overrideSessionId,
@@ -1367,6 +1762,56 @@ var tui = async (api) => {
1367
1762
  };
1368
1763
  api.slots.register(createSidebarSlot(api, signals));
1369
1764
  const KV_PREFIX = "cache_panel";
1765
+ const providerOptionTitle = (p, current) => {
1766
+ const zh = langZH();
1767
+ const hasManual = !!api.kv.get(`${KV_PREFIX}.balance.${p.id}.key`, "");
1768
+ const hasAuto = !hasManual && !!findOpencodeKey(api, p);
1769
+ const mark = hasManual ? zh ? "\uFF08\u7528\u6237 key\uFF09" : " (user key)" : hasAuto ? zh ? "\uFF08OpenCode\uFF09" : " (OpenCode)" : zh ? "\uFF08\u672A\u914D\u7F6E\uFF09" : " (not set)";
1770
+ return p.name + mark + (current && p.id === current ? " *" : "");
1771
+ };
1772
+ const promptBalanceKey = (dialog, provider) => {
1773
+ const zh = langZH();
1774
+ const current = api.kv.get(`${KV_PREFIX}.balance.${provider.id}.key`, "");
1775
+ const masked = maskKey(current);
1776
+ dialog?.replace(() => _$createComponent(api.ui.DialogPrompt, {
1777
+ get title() {
1778
+ return provider.name;
1779
+ },
1780
+ description: () => (() => {
1781
+ var _el$93 = _$createElement("text");
1782
+ _$insert(_el$93, () => zh ? `\u8F93\u5165 ${provider.name} API Key \u4EE5\u663E\u793A\u8D26\u6237\u4F59\u989D\uFF08\u7559\u7A7A\u6E05\u9664\uFF09` : `Enter your ${provider.name} API key to show account balance (leave empty to clear)`);
1783
+ return _el$93;
1784
+ })(),
1785
+ get placeholder() {
1786
+ return provider.keyPlaceholder ?? "sk-...";
1787
+ },
1788
+ value: masked,
1789
+ onConfirm: (val) => {
1790
+ const input = val.trim();
1791
+ let key;
1792
+ if (input === "") {
1793
+ key = "";
1794
+ } else if (input.includes("*")) {
1795
+ key = current;
1796
+ } else {
1797
+ key = input;
1798
+ }
1799
+ api.kv.set(`${KV_PREFIX}.balance.${provider.id}.key`, key);
1800
+ setBalanceRefresh((v) => v + 1);
1801
+ if (key) {
1802
+ api.ui.toast({
1803
+ message: zh ? "API Key \u5DF2\u4FDD\u5B58\uFF0C\u6B63\u5728\u67E5\u8BE2\u4F59\u989D..." : "API Key saved, fetching balance..."
1804
+ });
1805
+ } else {
1806
+ api.ui.toast({
1807
+ message: zh ? "API Key \u5DF2\u6E05\u9664" : "API Key cleared"
1808
+ });
1809
+ }
1810
+ dialog?.clear();
1811
+ },
1812
+ onCancel: () => dialog?.clear()
1813
+ }));
1814
+ };
1370
1815
  api.command?.register(() => [{
1371
1816
  title: "Cache: Set Currency",
1372
1817
  value: "cache.currency",
@@ -1388,6 +1833,8 @@ var tui = async (api) => {
1388
1833
  const defRate = DEFAULT_RATES[opt.value] ?? 1;
1389
1834
  api.kv.set(`${KV_PREFIX}.currency`, sym);
1390
1835
  api.kv.set(`${KV_PREFIX}.rate`, defRate);
1836
+ api.kv.set(`${KV_PREFIX}.balance_currency`, opt.value);
1837
+ signals.setBalanceCurrency(opt.value);
1391
1838
  signals.setCurrencySymbol(sym);
1392
1839
  signals.setExchangeRate(defRate);
1393
1840
  api.ui.toast({
@@ -1408,9 +1855,9 @@ var tui = async (api) => {
1408
1855
  dialog?.replace(() => _$createComponent(api.ui.DialogPrompt, {
1409
1856
  title: "Exchange Rate",
1410
1857
  description: () => (() => {
1411
- var _el$78 = _$createElement("text");
1412
- _$insertNode(_el$78, _$createTextNode(`Enter the exchange rate from USD to your currency (e.g. 7.2 for CNY)`));
1413
- return _el$78;
1858
+ var _el$94 = _$createElement("text");
1859
+ _$insertNode(_el$94, _$createTextNode(`Enter the exchange rate from USD to your currency (e.g. 7.2 for CNY)`));
1860
+ return _el$94;
1414
1861
  })(),
1415
1862
  placeholder: "1.0",
1416
1863
  get value() {
@@ -1441,6 +1888,7 @@ var tui = async (api) => {
1441
1888
  const modelOn = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true));
1442
1889
  const distOn = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true));
1443
1890
  const skillsOn = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true));
1891
+ const balanceOn = Boolean(api.kv.get(`${KV_PREFIX}.section.balance`, true));
1444
1892
  const borderOn = Boolean(api.kv.get(`${KV_PREFIX}.border`, true));
1445
1893
  dialog?.replace(() => _$createComponent(api.ui.DialogSelect, {
1446
1894
  title: "Toggle Section",
@@ -1456,6 +1904,9 @@ var tui = async (api) => {
1456
1904
  }, {
1457
1905
  title: `Loaded Skills [${skillsOn ? "ON" : "OFF"}]`,
1458
1906
  value: "skills"
1907
+ }, {
1908
+ title: `Balance [${balanceOn ? "ON" : "OFF"}]`,
1909
+ value: "balance"
1459
1910
  }, {
1460
1911
  title: `Panel Border [${borderOn ? "ON" : "OFF"}]`,
1461
1912
  value: "border"
@@ -1476,6 +1927,7 @@ var tui = async (api) => {
1476
1927
  if (opt.value === "model") signals.setSectionModel(!cur);
1477
1928
  if (opt.value === "dist") signals.setSectionDist(!cur);
1478
1929
  if (opt.value === "skills") signals.setSectionSkills(!cur);
1930
+ if (opt.value === "balance") signals.setSectionBalance(!cur);
1479
1931
  api.ui.toast({
1480
1932
  message: `${opt.value} section ${!cur ? "shown" : "hidden"}`
1481
1933
  });
@@ -1498,9 +1950,10 @@ var tui = async (api) => {
1498
1950
  const model = Boolean(api.kv.get(`${KV_PREFIX}.section.model`, true));
1499
1951
  const dist = Boolean(api.kv.get(`${KV_PREFIX}.section.dist`, true));
1500
1952
  const skills = Boolean(api.kv.get(`${KV_PREFIX}.section.skills`, true));
1953
+ const balance = Boolean(api.kv.get(`${KV_PREFIX}.section.balance`, true));
1501
1954
  api.ui.toast({
1502
1955
  title: "Cache Panel Config",
1503
- message: `Currency: ${sym} | Rate: ${rate} | Detail: ${detail ? "ON" : "OFF"} | Model: ${model ? "ON" : "OFF"} | Dist: ${dist ? "ON" : "OFF"} | Skills: ${skills ? "ON" : "OFF"}`,
1956
+ message: `Currency: ${sym} | Rate: ${rate} | Detail: ${detail ? "ON" : "OFF"} | Model: ${model ? "ON" : "OFF"} | Dist: ${dist ? "ON" : "OFF"} | Skills: ${skills ? "ON" : "OFF"} | Balance: ${balance ? "ON" : "OFF"}`,
1504
1957
  duration: 8e3
1505
1958
  });
1506
1959
  dialog?.clear();
@@ -1534,6 +1987,86 @@ var tui = async (api) => {
1534
1987
  }
1535
1988
  }));
1536
1989
  }
1990
+ }, {
1991
+ title: "Cache: Switch Balance Provider",
1992
+ value: "cache.balance",
1993
+ description: "\u5207\u6362\u4F59\u989D\u63D0\u4F9B\u5546 / \u81EA\u52A8\u5207\u6362\u5F53\u524D\u4F1A\u8BDD\u63D0\u4F9B\u5546 | Switch balance provider / auto-switch session provider",
1994
+ slash: {
1995
+ name: "cache-balance"
1996
+ },
1997
+ onSelect: (dialog) => {
1998
+ const zh = langZH();
1999
+ const current = signals.balanceProviderId();
2000
+ const auto = signals.autoBalance();
2001
+ const autoLabel = auto ? zh ? "\u81EA\u52A8\u5207\u6362\u63D0\u4F9B\u5546 [\u5F00]" : "Auto-switch provider [ON]" : zh ? "\u81EA\u52A8\u5207\u6362\u63D0\u4F9B\u5546 [\u5173]" : "Auto-switch provider [OFF]";
2002
+ dialog?.replace(() => _$createComponent(api.ui.DialogSelect, {
2003
+ title: zh ? "\u4F59\u989D\u63D0\u4F9B\u5546 / \u81EA\u52A8\u5207\u6362" : "Balance Provider / Auto-switch",
2004
+ get options() {
2005
+ return [{
2006
+ title: autoLabel,
2007
+ value: "__auto__"
2008
+ }, ...balanceProviders.map((p) => ({
2009
+ title: providerOptionTitle(p, current),
2010
+ value: p.id
2011
+ }))];
2012
+ },
2013
+ onSelect: (opt) => {
2014
+ if (opt.value === "__auto__") {
2015
+ const next = !auto;
2016
+ api.kv.set(`${KV_PREFIX}.balance.auto`, next);
2017
+ signals.setAutoBalance(next);
2018
+ api.ui.toast({
2019
+ message: zh ? `\u81EA\u52A8\u5207\u6362\u4F59\u989D\u63D0\u4F9B\u5546: ${next ? "\u5F00" : "\u5173"}` : `Auto-switch balance provider: ${next ? "ON" : "OFF"}`
2020
+ });
2021
+ dialog?.clear();
2022
+ } else {
2023
+ const provider = getBalanceProvider(opt.value);
2024
+ api.kv.set(`${KV_PREFIX}.balance.provider`, provider.id);
2025
+ api.kv.set(`${KV_PREFIX}.balance.auto`, false);
2026
+ signals.setBalanceProviderId(provider.id);
2027
+ signals.setAutoBalance(false);
2028
+ signals.setBalanceRefresh(signals.balanceRefresh() + 1);
2029
+ const hasKey = !!api.kv.get(`${KV_PREFIX}.balance.${provider.id}.key`, "");
2030
+ if (!hasKey) {
2031
+ promptBalanceKey(dialog, provider);
2032
+ } else {
2033
+ api.ui.toast({
2034
+ message: zh ? `\u4F59\u989D\u63D0\u4F9B\u5546: ${provider.name}\uFF08\u81EA\u52A8\u5207\u6362\u5DF2\u5173\u95ED\uFF09` : `Balance provider: ${provider.name} (auto-switch off)`
2035
+ });
2036
+ dialog?.clear();
2037
+ }
2038
+ }
2039
+ }
2040
+ }));
2041
+ }
2042
+ }, {
2043
+ title: "Cache: Set Balance API Key",
2044
+ value: "cache.balance.key",
2045
+ description: "Select a provider and set its API key for balance display",
2046
+ slash: {
2047
+ name: "cache-balance-key"
2048
+ },
2049
+ onSelect: (dialog) => {
2050
+ const zh = langZH();
2051
+ dialog?.replace(() => _$createComponent(api.ui.DialogSelect, {
2052
+ title: zh ? "\u9009\u62E9\u4F59\u989D\u63D0\u4F9B\u5546" : "Select Balance Provider",
2053
+ get options() {
2054
+ return balanceProviders.map((p) => ({
2055
+ title: providerOptionTitle(p),
2056
+ value: p.id
2057
+ }));
2058
+ },
2059
+ onSelect: (opt) => {
2060
+ const provider = getBalanceProvider(opt.value);
2061
+ api.kv.set(`${KV_PREFIX}.balance.provider`, provider.id);
2062
+ api.kv.set(`${KV_PREFIX}.balance.auto`, false);
2063
+ signals.setBalanceProviderId(provider.id);
2064
+ signals.setAutoBalance(false);
2065
+ signals.setBalanceRefresh(signals.balanceRefresh() + 1);
2066
+ promptBalanceKey(dialog, provider);
2067
+ }
2068
+ }));
2069
+ }
1537
2070
  }, {
1538
2071
  title: "Cache: Debug Skills Detection",
1539
2072
  value: "cache.debug-skills",
@@ -1684,9 +2217,9 @@ var tui = async (api) => {
1684
2217
  return signals.overrideSessionId() ? zh ? "\u5207\u6362\u5B50\u4EE3\u7406" : "Switch Sub" : zh ? "\u67E5\u770B\u5B50\u4EE3\u7406\u7F13\u5B58" : "View Sub Cache";
1685
2218
  },
1686
2219
  description: () => (() => {
1687
- var _el$80 = _$createElement("text");
1688
- _$insert(_el$80, zh ? "\u672A\u627E\u5230\u5B50\u4EE3\u7406\uFF0C\u8BF7\u624B\u52A8\u7C98\u8D34 Session ID" : "No sub-agents found. Paste a Session ID manually");
1689
- return _el$80;
2220
+ var _el$96 = _$createElement("text");
2221
+ _$insert(_el$96, zh ? "\u672A\u627E\u5230\u5B50\u4EE3\u7406\uFF0C\u8BF7\u624B\u52A8\u7C98\u8D34 Session ID" : "No sub-agents found. Paste a Session ID manually");
2222
+ return _el$96;
1690
2223
  })(),
1691
2224
  placeholder: "ses_...",
1692
2225
  get value() {