@thelioo/opencode-balancer 0.1.6 → 0.2.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.
Files changed (203) hide show
  1. package/INSTALL.txt +49 -21
  2. package/README.md +92 -50
  3. package/dist/core/accounts.ts +404 -0
  4. package/dist/core/database.ts +67 -0
  5. package/dist/core/events.ts +75 -0
  6. package/dist/core/native-auth-suppression.ts +36 -0
  7. package/dist/core/native-connect.ts +31 -0
  8. package/dist/core/path.ts +34 -0
  9. package/dist/core/pending.ts +351 -0
  10. package/dist/core/priority.ts +193 -0
  11. package/dist/core/schema.ts +439 -0
  12. package/dist/core/time.ts +3 -0
  13. package/dist/core/types.ts +72 -0
  14. package/dist/core/usage/index.ts +23 -0
  15. package/dist/core/usage/providers/copilot.ts +243 -0
  16. package/dist/core/usage/providers/openai.ts +179 -0
  17. package/dist/core/usage/redact.ts +80 -0
  18. package/dist/core/usage/store.ts +66 -0
  19. package/dist/core/usage/types.ts +24 -0
  20. package/dist/index.js +173 -4
  21. package/dist/index.js.map +1 -1
  22. package/dist/server/auth-watcher.ts +318 -0
  23. package/dist/server/commands.ts +58 -0
  24. package/dist/server/fetch-patch.ts +162 -0
  25. package/dist/server/index.ts +134 -0
  26. package/dist/server/native.ts +49 -0
  27. package/dist/server/request-balancer.ts +67 -0
  28. package/dist/tui/actions.ts +176 -108
  29. package/dist/tui/balancer-bar-sync.ts +55 -45
  30. package/dist/tui/components/alias-dialog.tsx +71 -56
  31. package/dist/tui/components/dashboard.tsx +530 -358
  32. package/dist/tui/components/priority-screen.tsx +389 -266
  33. package/dist/tui/components/provider-model-dialog.tsx +72 -55
  34. package/dist/tui/components/rename-dialog.tsx +35 -28
  35. package/dist/tui/components/sidebar.tsx +103 -79
  36. package/dist/tui/components/status-indicator.tsx +78 -59
  37. package/dist/tui/components/usage-bar.tsx +18 -7
  38. package/dist/tui/components/usage-display.tsx +32 -16
  39. package/dist/tui/connect.ts +105 -72
  40. package/dist/tui/dashboard-keys.ts +53 -41
  41. package/dist/tui/native-model-apply.ts +45 -36
  42. package/dist/tui/priority-keys.ts +44 -36
  43. package/dist/tui/provider-models.ts +32 -25
  44. package/dist/tui/responsive.ts +10 -7
  45. package/dist/tui/selected-account-bar-sync.ts +32 -0
  46. package/dist/tui/selection-colors.ts +38 -30
  47. package/dist/tui/state.ts +61 -44
  48. package/dist/tui/status-format.ts +24 -20
  49. package/dist/tui/tui.js +165 -120
  50. package/dist/tui/tui.js.map +1 -1
  51. package/dist/tui/tui.tsx +199 -117
  52. package/dist/tui/usage-auto-refresh.ts +52 -45
  53. package/dist/tui/usage-format.ts +9 -9
  54. package/package.json +61 -52
  55. package/dist/balancer/accounts.d.ts +0 -9
  56. package/dist/balancer/accounts.js +0 -102
  57. package/dist/balancer/accounts.js.map +0 -1
  58. package/dist/balancer/auth-watcher.d.ts +0 -1
  59. package/dist/balancer/auth-watcher.js +0 -30
  60. package/dist/balancer/auth-watcher.js.map +0 -1
  61. package/dist/balancer/commands.d.ts +0 -1
  62. package/dist/balancer/commands.js +0 -94
  63. package/dist/balancer/commands.js.map +0 -1
  64. package/dist/balancer/core.d.ts +0 -6
  65. package/dist/balancer/core.js +0 -7
  66. package/dist/balancer/core.js.map +0 -1
  67. package/dist/balancer/fetch-patch.d.ts +0 -1
  68. package/dist/balancer/fetch-patch.js +0 -110
  69. package/dist/balancer/fetch-patch.js.map +0 -1
  70. package/dist/balancer/http.d.ts +0 -40
  71. package/dist/balancer/http.js +0 -199
  72. package/dist/balancer/http.js.map +0 -1
  73. package/dist/balancer/native.d.ts +0 -3
  74. package/dist/balancer/native.js +0 -16
  75. package/dist/balancer/native.js.map +0 -1
  76. package/dist/balancer/state.d.ts +0 -14
  77. package/dist/balancer/state.js +0 -16
  78. package/dist/balancer/state.js.map +0 -1
  79. package/dist/balancer/storage.d.ts +0 -8
  80. package/dist/balancer/storage.js +0 -92
  81. package/dist/balancer/storage.js.map +0 -1
  82. package/dist/balancer/types.d.ts +0 -44
  83. package/dist/balancer/types.js +0 -2
  84. package/dist/balancer/types.js.map +0 -1
  85. package/dist/core/accounts.d.ts +0 -14
  86. package/dist/core/accounts.js +0 -260
  87. package/dist/core/accounts.js.map +0 -1
  88. package/dist/core/database.d.ts +0 -4
  89. package/dist/core/database.js +0 -69
  90. package/dist/core/database.js.map +0 -1
  91. package/dist/core/events.d.ts +0 -18
  92. package/dist/core/events.js +0 -39
  93. package/dist/core/events.js.map +0 -1
  94. package/dist/core/native-auth-suppression.d.ts +0 -3
  95. package/dist/core/native-auth-suppression.js +0 -19
  96. package/dist/core/native-auth-suppression.js.map +0 -1
  97. package/dist/core/native-connect.d.ts +0 -4
  98. package/dist/core/native-connect.js +0 -19
  99. package/dist/core/native-connect.js.map +0 -1
  100. package/dist/core/path.d.ts +0 -4
  101. package/dist/core/path.js +0 -26
  102. package/dist/core/path.js.map +0 -1
  103. package/dist/core/pending.d.ts +0 -9
  104. package/dist/core/pending.js +0 -237
  105. package/dist/core/pending.js.map +0 -1
  106. package/dist/core/priority.d.ts +0 -20
  107. package/dist/core/priority.js +0 -120
  108. package/dist/core/priority.js.map +0 -1
  109. package/dist/core/schema.d.ts +0 -2
  110. package/dist/core/schema.js +0 -265
  111. package/dist/core/schema.js.map +0 -1
  112. package/dist/core/time.d.ts +0 -1
  113. package/dist/core/time.js +0 -4
  114. package/dist/core/time.js.map +0 -1
  115. package/dist/core/types.d.ts +0 -59
  116. package/dist/core/types.js +0 -2
  117. package/dist/core/types.js.map +0 -1
  118. package/dist/core/usage/index.d.ts +0 -4
  119. package/dist/core/usage/index.js +0 -16
  120. package/dist/core/usage/index.js.map +0 -1
  121. package/dist/core/usage/providers/copilot.d.ts +0 -2
  122. package/dist/core/usage/providers/copilot.js +0 -169
  123. package/dist/core/usage/providers/copilot.js.map +0 -1
  124. package/dist/core/usage/providers/openai.d.ts +0 -2
  125. package/dist/core/usage/providers/openai.js +0 -133
  126. package/dist/core/usage/providers/openai.js.map +0 -1
  127. package/dist/core/usage/redact.d.ts +0 -3
  128. package/dist/core/usage/redact.js +0 -67
  129. package/dist/core/usage/redact.js.map +0 -1
  130. package/dist/core/usage/store.d.ts +0 -4
  131. package/dist/core/usage/store.js +0 -31
  132. package/dist/core/usage/store.js.map +0 -1
  133. package/dist/core/usage/types.d.ts +0 -21
  134. package/dist/core/usage/types.js +0 -2
  135. package/dist/core/usage/types.js.map +0 -1
  136. package/dist/index.d.ts +0 -5
  137. package/dist/server/auth-watcher.d.ts +0 -31
  138. package/dist/server/auth-watcher.js +0 -227
  139. package/dist/server/auth-watcher.js.map +0 -1
  140. package/dist/server/commands.d.ts +0 -2
  141. package/dist/server/commands.js +0 -46
  142. package/dist/server/commands.js.map +0 -1
  143. package/dist/server/fetch-patch.d.ts +0 -3
  144. package/dist/server/fetch-patch.js +0 -118
  145. package/dist/server/fetch-patch.js.map +0 -1
  146. package/dist/server/index.d.ts +0 -8
  147. package/dist/server/index.js +0 -94
  148. package/dist/server/index.js.map +0 -1
  149. package/dist/server/native.d.ts +0 -6
  150. package/dist/server/native.js +0 -35
  151. package/dist/server/native.js.map +0 -1
  152. package/dist/server/request-balancer.d.ts +0 -16
  153. package/dist/server/request-balancer.js +0 -43
  154. package/dist/server/request-balancer.js.map +0 -1
  155. package/dist/tui/actions.d.ts +0 -41
  156. package/dist/tui/actions.js +0 -88
  157. package/dist/tui/actions.js.map +0 -1
  158. package/dist/tui/balancer-bar-sync.d.ts +0 -19
  159. package/dist/tui/balancer-bar-sync.js +0 -45
  160. package/dist/tui/balancer-bar-sync.js.map +0 -1
  161. package/dist/tui/components/alias-dialog.d.ts +0 -4
  162. package/dist/tui/components/dashboard.d.ts +0 -12
  163. package/dist/tui/components/priority-screen.d.ts +0 -9
  164. package/dist/tui/components/provider-model-dialog.d.ts +0 -13
  165. package/dist/tui/components/rename-dialog.d.ts +0 -4
  166. package/dist/tui/components/sidebar.d.ts +0 -10
  167. package/dist/tui/components/status-indicator.d.ts +0 -9
  168. package/dist/tui/components/usage-bar.d.ts +0 -8
  169. package/dist/tui/components/usage-display.d.ts +0 -10
  170. package/dist/tui/connect.d.ts +0 -30
  171. package/dist/tui/connect.js +0 -73
  172. package/dist/tui/connect.js.map +0 -1
  173. package/dist/tui/dashboard-keys.d.ts +0 -45
  174. package/dist/tui/dashboard-keys.js +0 -44
  175. package/dist/tui/dashboard-keys.js.map +0 -1
  176. package/dist/tui/native-model-apply.d.ts +0 -21
  177. package/dist/tui/native-model-apply.js +0 -53
  178. package/dist/tui/native-model-apply.js.map +0 -1
  179. package/dist/tui/priority-keys.d.ts +0 -40
  180. package/dist/tui/priority-keys.js +0 -38
  181. package/dist/tui/priority-keys.js.map +0 -1
  182. package/dist/tui/provider-models.d.ts +0 -19
  183. package/dist/tui/provider-models.js +0 -17
  184. package/dist/tui/provider-models.js.map +0 -1
  185. package/dist/tui/responsive.d.ts +0 -9
  186. package/dist/tui/responsive.js +0 -13
  187. package/dist/tui/responsive.js.map +0 -1
  188. package/dist/tui/selection-colors.d.ts +0 -10
  189. package/dist/tui/selection-colors.js +0 -38
  190. package/dist/tui/selection-colors.js.map +0 -1
  191. package/dist/tui/state.d.ts +0 -14
  192. package/dist/tui/state.js +0 -46
  193. package/dist/tui/state.js.map +0 -1
  194. package/dist/tui/status-format.d.ts +0 -15
  195. package/dist/tui/status-format.js +0 -17
  196. package/dist/tui/status-format.js.map +0 -1
  197. package/dist/tui/tui.d.ts +0 -7
  198. package/dist/tui/usage-auto-refresh.d.ts +0 -16
  199. package/dist/tui/usage-auto-refresh.js +0 -46
  200. package/dist/tui/usage-auto-refresh.js.map +0 -1
  201. package/dist/tui/usage-format.d.ts +0 -2
  202. package/dist/tui/usage-format.js +0 -17
  203. package/dist/tui/usage-format.js.map +0 -1
package/dist/tui/tui.tsx CHANGED
@@ -1,142 +1,224 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
2
 
3
- import type { TuiPlugin, TuiPluginModule, TuiRouteCurrent } from "@opencode-ai/plugin/tui";
3
+ import type {
4
+ TuiPlugin,
5
+ TuiPluginModule,
6
+ TuiRouteCurrent,
7
+ } from "@opencode-ai/plugin/tui";
4
8
  import { createComponent } from "solid-js";
5
- import { setProviderModel } from "../core/priority";
9
+ import { getSelectedAccount, getSelectedModel } from "../core/accounts";
10
+ import { getBalancingEnabled, setProviderModel } from "../core/priority";
6
11
  import { activateAccount, removeAccountFromTui } from "./actions";
7
12
  import { createTuiBalancerBarSync } from "./balancer-bar-sync";
8
13
  import { openNativeConnect } from "./connect";
14
+ import { createNativeModelApplier } from "./native-model-apply";
15
+ import { providerModelOptions } from "./provider-models";
16
+ import { createSelectedAccountBarSync } from "./selected-account-bar-sync";
9
17
  import { createBalancerTuiState } from "./state";
10
18
  import { createUsageAutoRefresh } from "./usage-auto-refresh";
11
19
 
12
20
  type DashboardModule = typeof import("./components/dashboard");
13
21
  type PriorityScreenModule = typeof import("./components/priority-screen");
14
- type ProviderModelDialogModule = typeof import("./components/provider-model-dialog");
22
+ type ProviderModelDialogModule =
23
+ typeof import("./components/provider-model-dialog");
15
24
  type RenameDialogModule = typeof import("./components/rename-dialog");
16
25
  type SidebarModule = typeof import("./components/sidebar");
17
26
  type StatusIndicatorModule = typeof import("./components/status-indicator");
18
27
 
19
28
  function inferProviderID(session: unknown) {
20
- const providerID = (session as { model?: { providerID?: unknown } } | undefined)?.model?.providerID;
21
- return typeof providerID === "string" && providerID.length > 0 ? providerID : undefined;
29
+ const providerID = (
30
+ session as { model?: { providerID?: unknown } } | undefined
31
+ )?.model?.providerID;
32
+ return typeof providerID === "string" && providerID.length > 0
33
+ ? providerID
34
+ : undefined;
22
35
  }
23
36
 
24
37
  function copyRoute(route: TuiRouteCurrent): TuiRouteCurrent {
25
- return "params" in route && route.params
26
- ? { name: route.name, params: { ...route.params } }
27
- : { name: route.name };
38
+ return "params" in route && route.params
39
+ ? { name: route.name, params: { ...route.params } }
40
+ : { name: route.name };
28
41
  }
29
42
 
30
43
  const tui: TuiPlugin = async (api) => {
31
- await import("@opentui/solid/runtime-plugin" + "-support");
32
-
33
- const [dashboardModule, priorityScreenModule, providerModelDialogModule, renameDialogModule, sidebarModule, statusIndicatorModule] = await Promise.all([
34
- import("./components/dashboard" + ".tsx") as Promise<DashboardModule>,
35
- import("./components/priority-screen" + ".tsx") as Promise<PriorityScreenModule>,
36
- import("./components/provider-model-dialog" + ".tsx") as Promise<ProviderModelDialogModule>,
37
- import("./components/rename-dialog" + ".tsx") as Promise<RenameDialogModule>,
38
- import("./components/sidebar" + ".tsx") as Promise<SidebarModule>,
39
- import("./components/status-indicator" + ".tsx") as Promise<StatusIndicatorModule>,
40
- ]);
41
- const state = createBalancerTuiState();
42
- const usageAutoRefresh = createUsageAutoRefresh(api, state);
43
- const balancerBarSync = createTuiBalancerBarSync(api, state);
44
- let dashboardReturnRoute: TuiRouteCurrent | undefined;
45
-
46
- api.lifecycle.onDispose(() => {
47
- usageAutoRefresh.dispose();
48
- state.dispose();
49
- });
50
-
51
- const openDashboard = () => {
52
- if (api.route.current.name !== "balancer.dashboard") dashboardReturnRoute = copyRoute(api.route.current);
53
- api.route.navigate("balancer.dashboard");
54
- };
55
-
56
- const openPriority = () => {
57
- api.route.navigate("balancer.priority");
58
- };
59
-
60
- const backFromDashboard = () => {
61
- const route = dashboardReturnRoute;
62
- dashboardReturnRoute = undefined;
63
- if (route) api.route.navigate(route.name, "params" in route ? route.params : undefined);
64
- else api.route.navigate("home");
65
- };
66
-
67
- const unregisterDashboard = api.route.register([
68
- {
69
- name: "balancer.dashboard",
70
- render: () =>
71
- createComponent(dashboardModule.Dashboard, {
72
- api,
73
- state,
74
- onBack: backFromDashboard,
75
- openPriority,
76
- openConnect: () => openNativeConnect({ ...api, db: state.db }),
77
- renameAccount: (providerID, alias) => renameDialogModule.openRenameDialog(api, state, providerID, alias),
78
- removeAccount: (providerID, alias) => removeAccountFromTui(api, state, providerID, alias),
79
- }),
80
- },
81
- {
82
- name: "balancer.priority",
83
- render: () =>
84
- createComponent(priorityScreenModule.PriorityScreen, {
85
- api,
86
- state,
87
- onBack: () => api.route.navigate("balancer.dashboard"),
88
- openModelPicker: (providerID) =>
89
- providerModelDialogModule.openProviderModelDialog(api, state, providerID, {
90
- onSelected: (model) => setProviderModel(state.db, model.providerID, model.modelID),
91
- }),
92
- }),
93
- },
94
- ]);
95
- api.lifecycle.onDispose(unregisterDashboard);
96
-
97
- const unregisterKeymap = api.keymap.registerLayer({
98
- commands: [
99
- {
100
- name: "balancer.dashboard.open",
101
- title: "Open Balancer Dashboard",
102
- category: "Plugin",
103
- namespace: "palette",
104
- slashName: "balancer",
105
- run() {
106
- openDashboard();
107
- },
108
- },
109
- ],
110
- bindings: [{ key: "ctrl+b", cmd: "balancer.dashboard.open" }],
111
- });
112
- api.lifecycle.onDispose(unregisterKeymap);
113
-
114
- api.slots.register({
115
- slots: {
116
- session_prompt_right(_ctx, value) {
117
- void usageAutoRefresh.refreshForPrompt();
118
- void balancerBarSync.maybeSync();
119
- return createComponent(statusIndicatorModule.BalancerStatusIndicator, {
120
- api,
121
- state,
122
- providerID: () => inferProviderID(api.state.session.get(value.session_id)),
123
- });
124
- },
125
- sidebar_content(_ctx, value) {
126
- return createComponent(sidebarModule.BalancerSidebar, {
127
- api,
128
- state,
129
- openDashboard,
130
- activateAccount: (providerID, alias) =>
131
- activateAccount(api, state, providerID, alias, {
132
- sessionProviderID: inferProviderID(api.state.session.get(value.session_id)),
133
- openProviderModelPicker: (targetProviderID) =>
134
- providerModelDialogModule.openProviderModelDialog(api, state, targetProviderID),
135
- }),
136
- });
137
- },
138
- },
139
- });
44
+ await import("@opentui/solid/runtime-plugin" + "-support");
45
+
46
+ const [
47
+ dashboardModule,
48
+ priorityScreenModule,
49
+ providerModelDialogModule,
50
+ renameDialogModule,
51
+ sidebarModule,
52
+ statusIndicatorModule,
53
+ ] = await Promise.all([
54
+ import("./components/dashboard" + ".tsx") as Promise<DashboardModule>,
55
+ import(
56
+ "./components/priority-screen" + ".tsx"
57
+ ) as Promise<PriorityScreenModule>,
58
+ import(
59
+ "./components/provider-model-dialog" + ".tsx"
60
+ ) as Promise<ProviderModelDialogModule>,
61
+ import(
62
+ "./components/rename-dialog" + ".tsx"
63
+ ) as Promise<RenameDialogModule>,
64
+ import("./components/sidebar" + ".tsx") as Promise<SidebarModule>,
65
+ import(
66
+ "./components/status-indicator" + ".tsx"
67
+ ) as Promise<StatusIndicatorModule>,
68
+ ]);
69
+ const state = createBalancerTuiState();
70
+ const usageAutoRefresh = createUsageAutoRefresh(api, state);
71
+ const balancerBarSync = createTuiBalancerBarSync(api, state);
72
+ const nativeModelApplier = createNativeModelApplier(api);
73
+ let dashboardReturnRoute: TuiRouteCurrent | undefined;
74
+ let nativeProviderID: string | undefined;
75
+ let sessionProviderID: string | undefined;
76
+
77
+ const applyNativeProviderModel = async (providerID: string) => {
78
+ const modelOptions = providerModelOptions(api.state.provider, providerID);
79
+ const selected = getSelectedModel(state.db, providerID);
80
+ const option =
81
+ modelOptions.find((item) => item.modelID === selected?.modelID) ??
82
+ modelOptions[0];
83
+ if (!option) return false;
84
+ return nativeModelApplier(
85
+ { modelID: option.modelID, providerID: option.providerID },
86
+ option.title,
87
+ );
88
+ };
89
+
90
+ const applyNativeProviderModelAndTrack = async (providerID: string) => {
91
+ const applied = await applyNativeProviderModel(providerID);
92
+ if (applied) nativeProviderID = providerID;
93
+ return applied;
94
+ };
95
+
96
+ const selectedAccountBarSync = createSelectedAccountBarSync({
97
+ applyProvider: applyNativeProviderModelAndTrack,
98
+ currentProvider: () => nativeProviderID ?? sessionProviderID,
99
+ dialogOpen: () => api.ui.dialog.open,
100
+ selectedProvider: () =>
101
+ getBalancingEnabled(state.db)
102
+ ? undefined
103
+ : getSelectedAccount(state.db)?.providerID,
104
+ });
105
+
106
+ api.lifecycle.onDispose(() => {
107
+ usageAutoRefresh.dispose();
108
+ state.dispose();
109
+ });
110
+
111
+ const openDashboard = () => {
112
+ if (api.route.current.name !== "balancer.dashboard")
113
+ dashboardReturnRoute = copyRoute(api.route.current);
114
+ api.route.navigate("balancer.dashboard");
115
+ };
116
+
117
+ const openPriority = () => {
118
+ api.route.navigate("balancer.priority");
119
+ };
120
+
121
+ const backFromDashboard = () => {
122
+ const route = dashboardReturnRoute;
123
+ dashboardReturnRoute = undefined;
124
+ if (route)
125
+ api.route.navigate(
126
+ route.name,
127
+ "params" in route ? route.params : undefined,
128
+ );
129
+ else api.route.navigate("home");
130
+ };
131
+
132
+ const unregisterDashboard = api.route.register([
133
+ {
134
+ name: "balancer.dashboard",
135
+ render: () =>
136
+ createComponent(dashboardModule.Dashboard, {
137
+ api,
138
+ onBack: backFromDashboard,
139
+ openConnect: () => openNativeConnect({ ...api, db: state.db }),
140
+ openPriority,
141
+ removeAccount: (providerID, alias) =>
142
+ removeAccountFromTui(api, state, providerID, alias),
143
+ renameAccount: (providerID, alias) =>
144
+ renameDialogModule.openRenameDialog(api, state, providerID, alias),
145
+ state,
146
+ }),
147
+ },
148
+ {
149
+ name: "balancer.priority",
150
+ render: () =>
151
+ createComponent(priorityScreenModule.PriorityScreen, {
152
+ api,
153
+ onBack: () => api.route.navigate("balancer.dashboard"),
154
+ openModelPicker: (providerID, onComplete) =>
155
+ providerModelDialogModule.openProviderModelDialog(
156
+ api,
157
+ state,
158
+ providerID,
159
+ {
160
+ applyNativeSelection: false,
161
+ onComplete,
162
+ onSelected: (model) =>
163
+ setProviderModel(state.db, model.providerID, model.modelID),
164
+ },
165
+ ),
166
+ state,
167
+ }),
168
+ },
169
+ ]);
170
+ api.lifecycle.onDispose(unregisterDashboard);
171
+
172
+ const unregisterKeymap = api.keymap.registerLayer({
173
+ bindings: [{ cmd: "balancer.dashboard.open", key: "ctrl+b" }],
174
+ commands: [
175
+ {
176
+ category: "Plugin",
177
+ name: "balancer.dashboard.open",
178
+ namespace: "palette",
179
+ run() {
180
+ openDashboard();
181
+ },
182
+ slashName: "balancer",
183
+ title: "Open Balancer Dashboard",
184
+ },
185
+ ],
186
+ });
187
+ api.lifecycle.onDispose(unregisterKeymap);
188
+
189
+ api.slots.register({
190
+ slots: {
191
+ session_prompt_right(_ctx, value) {
192
+ sessionProviderID = inferProviderID(
193
+ api.state.session.get(value.session_id),
194
+ );
195
+ void usageAutoRefresh.refreshForPrompt();
196
+ void selectedAccountBarSync.maybeSync();
197
+ void balancerBarSync.maybeSync();
198
+ return createComponent(statusIndicatorModule.BalancerStatusIndicator, {
199
+ api,
200
+ providerID: () =>
201
+ inferProviderID(api.state.session.get(value.session_id)),
202
+ state,
203
+ });
204
+ },
205
+ sidebar_content(_ctx, value) {
206
+ return createComponent(sidebarModule.BalancerSidebar, {
207
+ activateAccount: (providerID, alias) => {
208
+ return activateAccount(api, state, providerID, alias, {
209
+ applyNativeProviderModel: applyNativeProviderModelAndTrack,
210
+ sessionProviderID:
211
+ nativeProviderID ??
212
+ inferProviderID(api.state.session.get(value.session_id)),
213
+ });
214
+ },
215
+ api,
216
+ openDashboard,
217
+ state,
218
+ });
219
+ },
220
+ },
221
+ });
140
222
  };
141
223
 
142
224
  export default { id: "opencode-balancer", tui } satisfies TuiPluginModule;
@@ -1,64 +1,71 @@
1
1
  import { listAccounts } from "../core/accounts";
2
- import { refreshAccountUsage } from "../core/usage";
2
+ import type { refreshAccountUsage } from "../core/usage";
3
3
  import { refreshUsageForAccount } from "./actions";
4
4
  import type { BalancerTuiState } from "./state";
5
5
 
6
6
  type ToastApi = Parameters<typeof refreshUsageForAccount>[0];
7
7
 
8
8
  type UsageAutoRefreshOptions = {
9
- intervalMs?: number;
10
- promptDebounceMs?: number;
11
- refreshUsage?: typeof refreshAccountUsage;
12
- now?: () => number;
9
+ intervalMs?: number;
10
+ promptDebounceMs?: number;
11
+ refreshUsage?: typeof refreshAccountUsage;
12
+ now?: () => number;
13
13
  };
14
14
 
15
15
  export function createUsageAutoRefresh(
16
- api: ToastApi,
17
- state: BalancerTuiState,
18
- options: UsageAutoRefreshOptions = {},
16
+ api: ToastApi,
17
+ state: BalancerTuiState,
18
+ options: UsageAutoRefreshOptions = {},
19
19
  ) {
20
- const intervalMs = options.intervalMs ?? 60_000;
21
- const promptDebounceMs = options.promptDebounceMs ?? 30_000;
22
- const now = options.now ?? Date.now;
23
- const inFlight = new Set<string>();
24
- let lastPromptRefreshAt = 0;
20
+ const intervalMs = options.intervalMs ?? 60_000;
21
+ const promptDebounceMs = options.promptDebounceMs ?? 30_000;
22
+ const now = options.now ?? Date.now;
23
+ const inFlight = new Set<string>();
24
+ let lastPromptRefreshAt = 0;
25
25
 
26
- const refreshOne = async (providerID: string, alias: string) => {
27
- const key = `${providerID}/${alias}`;
28
- if (inFlight.has(key)) return;
26
+ const refreshOne = async (providerID: string, alias: string) => {
27
+ const key = `${providerID}/${alias}`;
28
+ if (inFlight.has(key)) return;
29
29
 
30
- inFlight.add(key);
31
- try {
32
- await refreshUsageForAccount(api, state, providerID, alias, {
33
- refreshUsage: options.refreshUsage,
34
- silent: true,
35
- });
36
- } finally {
37
- inFlight.delete(key);
38
- }
39
- };
30
+ inFlight.add(key);
31
+ try {
32
+ await refreshUsageForAccount(api, state, providerID, alias, {
33
+ refreshUsage: options.refreshUsage,
34
+ silent: true,
35
+ });
36
+ } finally {
37
+ inFlight.delete(key);
38
+ }
39
+ };
40
40
 
41
- const refreshNow = async () => {
42
- const accounts = listAccounts(state.db).filter((account) => !account.disabled);
43
- await Promise.all(accounts.map((account) => refreshOne(account.providerID, account.alias)));
44
- };
41
+ const refreshNow = async () => {
42
+ const accounts = listAccounts(state.db).filter(
43
+ (account) => !account.disabled,
44
+ );
45
+ await Promise.all(
46
+ accounts.map((account) => refreshOne(account.providerID, account.alias)),
47
+ );
48
+ };
45
49
 
46
- const refreshForPrompt = async () => {
47
- const current = now();
48
- if (current - lastPromptRefreshAt < promptDebounceMs) return;
50
+ const refreshForPrompt = async () => {
51
+ const current = now();
52
+ if (current - lastPromptRefreshAt < promptDebounceMs) return;
49
53
 
50
- lastPromptRefreshAt = current;
51
- await refreshNow();
52
- };
54
+ lastPromptRefreshAt = current;
55
+ await refreshNow();
56
+ };
53
57
 
54
- const timer = intervalMs > 0 ? setInterval(() => void refreshNow(), intervalMs) : undefined;
55
- void refreshNow();
58
+ const timer =
59
+ intervalMs > 0
60
+ ? setInterval(() => void refreshNow(), intervalMs)
61
+ : undefined;
62
+ void refreshNow();
56
63
 
57
- return {
58
- refreshNow,
59
- refreshForPrompt,
60
- dispose() {
61
- if (timer) clearInterval(timer);
62
- },
63
- };
64
+ return {
65
+ dispose() {
66
+ if (timer) clearInterval(timer);
67
+ },
68
+ refreshForPrompt,
69
+ refreshNow,
70
+ };
64
71
  }
@@ -1,14 +1,14 @@
1
1
  export function formatUsageBar(percent: number | undefined, width = 8) {
2
- if (percent === undefined) return `${"─".repeat(width)} --`;
3
- const value = Math.max(0, Math.min(100, Math.round(percent)));
4
- const filled = Math.max(0, Math.min(width, Math.ceil((value / 100) * width)));
5
- return `${"█".repeat(filled)}${"░".repeat(width - filled)} ${value}%`;
2
+ if (percent === undefined) return `${"─".repeat(width)} --`;
3
+ const value = Math.max(0, Math.min(100, Math.round(percent)));
4
+ const filled = Math.max(0, Math.min(width, Math.ceil((value / 100) * width)));
5
+ return `${"█".repeat(filled)}${"░".repeat(width - filled)} ${value}%`;
6
6
  }
7
7
 
8
8
  export function truncateMiddle(value: string, maxLength: number) {
9
- if (value.length <= maxLength) return value;
10
- if (maxLength <= 1) return "…";
11
- const left = Math.ceil((maxLength - 1) / 2);
12
- const right = Math.floor((maxLength - 1) / 2);
13
- return `${value.slice(0, left)}…${value.slice(value.length - right)}`;
9
+ if (value.length <= maxLength) return value;
10
+ if (maxLength <= 1) return "…";
11
+ const left = Math.ceil((maxLength - 1) / 2);
12
+ const right = Math.floor((maxLength - 1) / 2);
13
+ return `${value.slice(0, left)}…${value.slice(value.length - right)}`;
14
14
  }
package/package.json CHANGED
@@ -1,54 +1,63 @@
1
1
  {
2
- "name": "@thelioo/opencode-balancer",
3
- "version": "0.1.6",
4
- "description": "Account balancer plugin for opencode.",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "default": "./dist/index.js"
12
- },
13
- "./tui": {
14
- "types": "./dist/tui/tui.d.ts",
15
- "default": "./dist/tui/tui.tsx"
16
- }
17
- },
18
- "files": [
19
- "dist",
20
- "README.md",
21
- "INSTALL.txt",
22
- "LICENSE"
23
- ],
24
- "scripts": {
25
- "build": "tsc --project tsconfig.json && node scripts/copy-tui-source.mjs",
26
- "check": "tsc --noEmit --project tsconfig.json",
27
- "test": "bun test",
28
- "prepublishOnly": "npm run build"
29
- },
30
- "keywords": [
31
- "opencode",
32
- "opencode-plugin",
33
- "balancer",
34
- "accounts"
35
- ],
36
- "repository": {
37
- "type": "git",
38
- "url": "git+https://github.com/thelioo/opencode-balancer.git"
39
- },
40
- "bugs": {
41
- "url": "https://github.com/thelioo/opencode-balancer/issues"
42
- },
43
- "homepage": "https://github.com/thelioo/opencode-balancer#readme",
44
- "license": "MIT",
45
- "dependencies": {
46
- "@opencode-ai/plugin": "^1.14.51",
47
- "@opentui/solid": "^0.2.16",
48
- "solid-js": "^1.9.12"
49
- },
50
- "devDependencies": {
51
- "@types/bun": "^1.3.14",
52
- "typescript": "^5.9.3"
53
- }
2
+ "bugs": {
3
+ "url": "https://github.com/thelioo/opencode-balancer/issues"
4
+ },
5
+ "dependencies": {
6
+ "@opencode-ai/plugin": "^1.14.51",
7
+ "@opentui/solid": "^0.2.16",
8
+ "solid-js": "^1.9.12"
9
+ },
10
+ "description": "Account balancer plugin for opencode.",
11
+ "devDependencies": {
12
+ "@biomejs/biome": "^2.4.16",
13
+ "@changesets/cli": "^2.31.0",
14
+ "@types/bun": "^1.3.14",
15
+ "@typescript/native-preview": "7.0.0-dev.20260607.1",
16
+ "concurrently": "^10.0.3",
17
+ "husky": "^9.1.7"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "default": "./dist/index.js"
22
+ },
23
+ "./tui": {
24
+ "default": "./dist/tui/tui.tsx"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md",
30
+ "INSTALL.txt",
31
+ "LICENSE"
32
+ ],
33
+ "homepage": "https://github.com/thelioo/opencode-balancer#readme",
34
+ "keywords": [
35
+ "opencode",
36
+ "opencode-plugin",
37
+ "balancer",
38
+ "accounts"
39
+ ],
40
+ "license": "MIT",
41
+ "main": "./dist/index.js",
42
+ "name": "@thelioo/opencode-balancer",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/thelioo/opencode-balancer.git"
46
+ },
47
+ "scripts": {
48
+ "build": "bun scripts/build.ts",
49
+ "changeset": "changeset",
50
+ "checktypes": "tsgo --noEmit",
51
+ "format": "biome format --write",
52
+ "lint": "biome check .",
53
+ "lint:fix": "biome check . --fix",
54
+ "lint:unsafe": "biome check . --fix --unsafe",
55
+ "prepare": "husky",
56
+ "prepublishOnly": "bun run build",
57
+ "release": "changeset publish",
58
+ "test": "bun test",
59
+ "version": "changeset version"
60
+ },
61
+ "type": "module",
62
+ "version": "0.2.0"
54
63
  }
@@ -1,9 +0,0 @@
1
- import type { Account, AuthInfo, ProviderState } from "./types";
2
- export declare function normalizeAlias(alias: string): string;
3
- export declare function saveAccount(providerID: string, aliasInput: string, auth: AuthInfo): Promise<Account>;
4
- export declare function getProviderState(providerID: string): Promise<ProviderState | undefined>;
5
- export declare function getActiveAccount(providerID: string): Promise<Account | undefined>;
6
- export declare function listAccountsText(): Promise<string>;
7
- export declare function nextAvailableAccount(providerID: string, currentAlias?: string): Promise<Account | undefined>;
8
- export declare function markRateLimited(providerID: string, alias: string, retryAfterMs?: number): Promise<void>;
9
- export declare function markUsed(providerID: string, alias: string): Promise<void>;