@thelioo/opencode-balancer 0.1.8 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/INSTALL.txt +53 -25
  2. package/README.md +95 -51
  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 -112
  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 -267
  33. package/dist/tui/components/provider-model-dialog.tsx +71 -64
  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 +104 -73
  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 +23 -23
  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 -153
  50. package/dist/tui/tui.js.map +1 -1
  51. package/dist/tui/tui.tsx +194 -144
  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/core/accounts.d.ts +0 -14
  56. package/dist/core/accounts.js +0 -260
  57. package/dist/core/accounts.js.map +0 -1
  58. package/dist/core/database.d.ts +0 -4
  59. package/dist/core/database.js +0 -69
  60. package/dist/core/database.js.map +0 -1
  61. package/dist/core/events.d.ts +0 -18
  62. package/dist/core/events.js +0 -39
  63. package/dist/core/events.js.map +0 -1
  64. package/dist/core/native-auth-suppression.d.ts +0 -3
  65. package/dist/core/native-auth-suppression.js +0 -19
  66. package/dist/core/native-auth-suppression.js.map +0 -1
  67. package/dist/core/native-connect.d.ts +0 -4
  68. package/dist/core/native-connect.js +0 -19
  69. package/dist/core/native-connect.js.map +0 -1
  70. package/dist/core/path.d.ts +0 -4
  71. package/dist/core/path.js +0 -26
  72. package/dist/core/path.js.map +0 -1
  73. package/dist/core/pending.d.ts +0 -9
  74. package/dist/core/pending.js +0 -237
  75. package/dist/core/pending.js.map +0 -1
  76. package/dist/core/priority.d.ts +0 -20
  77. package/dist/core/priority.js +0 -120
  78. package/dist/core/priority.js.map +0 -1
  79. package/dist/core/schema.d.ts +0 -2
  80. package/dist/core/schema.js +0 -265
  81. package/dist/core/schema.js.map +0 -1
  82. package/dist/core/time.d.ts +0 -1
  83. package/dist/core/time.js +0 -4
  84. package/dist/core/time.js.map +0 -1
  85. package/dist/core/types.d.ts +0 -59
  86. package/dist/core/types.js +0 -2
  87. package/dist/core/types.js.map +0 -1
  88. package/dist/core/usage/index.d.ts +0 -4
  89. package/dist/core/usage/index.js +0 -16
  90. package/dist/core/usage/index.js.map +0 -1
  91. package/dist/core/usage/providers/copilot.d.ts +0 -2
  92. package/dist/core/usage/providers/copilot.js +0 -169
  93. package/dist/core/usage/providers/copilot.js.map +0 -1
  94. package/dist/core/usage/providers/openai.d.ts +0 -2
  95. package/dist/core/usage/providers/openai.js +0 -133
  96. package/dist/core/usage/providers/openai.js.map +0 -1
  97. package/dist/core/usage/redact.d.ts +0 -3
  98. package/dist/core/usage/redact.js +0 -67
  99. package/dist/core/usage/redact.js.map +0 -1
  100. package/dist/core/usage/store.d.ts +0 -4
  101. package/dist/core/usage/store.js +0 -31
  102. package/dist/core/usage/store.js.map +0 -1
  103. package/dist/core/usage/types.d.ts +0 -21
  104. package/dist/core/usage/types.js +0 -2
  105. package/dist/core/usage/types.js.map +0 -1
  106. package/dist/index.d.ts +0 -5
  107. package/dist/server/auth-watcher.d.ts +0 -32
  108. package/dist/server/auth-watcher.js +0 -227
  109. package/dist/server/auth-watcher.js.map +0 -1
  110. package/dist/server/commands.d.ts +0 -2
  111. package/dist/server/commands.js +0 -46
  112. package/dist/server/commands.js.map +0 -1
  113. package/dist/server/fetch-patch.d.ts +0 -3
  114. package/dist/server/fetch-patch.js +0 -118
  115. package/dist/server/fetch-patch.js.map +0 -1
  116. package/dist/server/index.d.ts +0 -8
  117. package/dist/server/index.js +0 -94
  118. package/dist/server/index.js.map +0 -1
  119. package/dist/server/native.d.ts +0 -6
  120. package/dist/server/native.js +0 -35
  121. package/dist/server/native.js.map +0 -1
  122. package/dist/server/request-balancer.d.ts +0 -16
  123. package/dist/server/request-balancer.js +0 -43
  124. package/dist/server/request-balancer.js.map +0 -1
  125. package/dist/tui/actions.d.ts +0 -41
  126. package/dist/tui/actions.js +0 -92
  127. package/dist/tui/actions.js.map +0 -1
  128. package/dist/tui/balancer-bar-sync.d.ts +0 -19
  129. package/dist/tui/balancer-bar-sync.js +0 -45
  130. package/dist/tui/balancer-bar-sync.js.map +0 -1
  131. package/dist/tui/components/alias-dialog.d.ts +0 -4
  132. package/dist/tui/components/dashboard.d.ts +0 -12
  133. package/dist/tui/components/priority-screen.d.ts +0 -9
  134. package/dist/tui/components/provider-model-dialog.d.ts +0 -14
  135. package/dist/tui/components/rename-dialog.d.ts +0 -4
  136. package/dist/tui/components/sidebar.d.ts +0 -10
  137. package/dist/tui/components/status-indicator.d.ts +0 -9
  138. package/dist/tui/components/usage-bar.d.ts +0 -8
  139. package/dist/tui/components/usage-display.d.ts +0 -10
  140. package/dist/tui/connect.d.ts +0 -30
  141. package/dist/tui/connect.js +0 -75
  142. package/dist/tui/connect.js.map +0 -1
  143. package/dist/tui/dashboard-keys.d.ts +0 -45
  144. package/dist/tui/dashboard-keys.js +0 -44
  145. package/dist/tui/dashboard-keys.js.map +0 -1
  146. package/dist/tui/native-model-apply.d.ts +0 -21
  147. package/dist/tui/native-model-apply.js +0 -53
  148. package/dist/tui/native-model-apply.js.map +0 -1
  149. package/dist/tui/priority-keys.d.ts +0 -40
  150. package/dist/tui/priority-keys.js +0 -38
  151. package/dist/tui/priority-keys.js.map +0 -1
  152. package/dist/tui/provider-models.d.ts +0 -19
  153. package/dist/tui/provider-models.js +0 -17
  154. package/dist/tui/provider-models.js.map +0 -1
  155. package/dist/tui/responsive.d.ts +0 -9
  156. package/dist/tui/responsive.js +0 -13
  157. package/dist/tui/responsive.js.map +0 -1
  158. package/dist/tui/selected-account-bar-sync.d.ts +0 -10
  159. package/dist/tui/selected-account-bar-sync.js +0 -26
  160. package/dist/tui/selected-account-bar-sync.js.map +0 -1
  161. package/dist/tui/selection-colors.d.ts +0 -10
  162. package/dist/tui/selection-colors.js +0 -38
  163. package/dist/tui/selection-colors.js.map +0 -1
  164. package/dist/tui/state.d.ts +0 -14
  165. package/dist/tui/state.js +0 -46
  166. package/dist/tui/state.js.map +0 -1
  167. package/dist/tui/status-format.d.ts +0 -15
  168. package/dist/tui/status-format.js +0 -17
  169. package/dist/tui/status-format.js.map +0 -1
  170. package/dist/tui/tui.d.ts +0 -7
  171. package/dist/tui/usage-auto-refresh.d.ts +0 -16
  172. package/dist/tui/usage-auto-refresh.js +0 -46
  173. package/dist/tui/usage-auto-refresh.js.map +0 -1
  174. package/dist/tui/usage-format.d.ts +0 -2
  175. package/dist/tui/usage-format.js +0 -17
  176. package/dist/tui/usage-format.js.map +0 -1
package/dist/tui/tui.tsx CHANGED
@@ -1,6 +1,10 @@
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
9
  import { getSelectedAccount, getSelectedModel } from "../core/accounts";
6
10
  import { getBalancingEnabled, setProviderModel } from "../core/priority";
@@ -15,160 +19,206 @@ import { createUsageAutoRefresh } from "./usage-auto-refresh";
15
19
 
16
20
  type DashboardModule = typeof import("./components/dashboard");
17
21
  type PriorityScreenModule = typeof import("./components/priority-screen");
18
- type ProviderModelDialogModule = typeof import("./components/provider-model-dialog");
22
+ type ProviderModelDialogModule =
23
+ typeof import("./components/provider-model-dialog");
19
24
  type RenameDialogModule = typeof import("./components/rename-dialog");
20
25
  type SidebarModule = typeof import("./components/sidebar");
21
26
  type StatusIndicatorModule = typeof import("./components/status-indicator");
22
27
 
23
28
  function inferProviderID(session: unknown) {
24
- const providerID = (session as { model?: { providerID?: unknown } } | undefined)?.model?.providerID;
25
- 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;
26
35
  }
27
36
 
28
37
  function copyRoute(route: TuiRouteCurrent): TuiRouteCurrent {
29
- return "params" in route && route.params
30
- ? { name: route.name, params: { ...route.params } }
31
- : { name: route.name };
38
+ return "params" in route && route.params
39
+ ? { name: route.name, params: { ...route.params } }
40
+ : { name: route.name };
32
41
  }
33
42
 
34
43
  const tui: TuiPlugin = async (api) => {
35
- await import("@opentui/solid/runtime-plugin" + "-support");
36
-
37
- const [dashboardModule, priorityScreenModule, providerModelDialogModule, renameDialogModule, sidebarModule, statusIndicatorModule] = await Promise.all([
38
- import("./components/dashboard" + ".tsx") as Promise<DashboardModule>,
39
- import("./components/priority-screen" + ".tsx") as Promise<PriorityScreenModule>,
40
- import("./components/provider-model-dialog" + ".tsx") as Promise<ProviderModelDialogModule>,
41
- import("./components/rename-dialog" + ".tsx") as Promise<RenameDialogModule>,
42
- import("./components/sidebar" + ".tsx") as Promise<SidebarModule>,
43
- import("./components/status-indicator" + ".tsx") as Promise<StatusIndicatorModule>,
44
- ]);
45
- const state = createBalancerTuiState();
46
- const usageAutoRefresh = createUsageAutoRefresh(api, state);
47
- const balancerBarSync = createTuiBalancerBarSync(api, state);
48
- const nativeModelApplier = createNativeModelApplier(api);
49
- let dashboardReturnRoute: TuiRouteCurrent | undefined;
50
- let nativeProviderID: string | undefined;
51
- let sessionProviderID: string | undefined;
52
-
53
- const applyNativeProviderModel = async (providerID: string) => {
54
- const modelOptions = providerModelOptions(api.state.provider, providerID);
55
- const selected = getSelectedModel(state.db, providerID);
56
- const option = modelOptions.find((item) => item.modelID === selected?.modelID) ?? modelOptions[0];
57
- if (!option) return false;
58
- return nativeModelApplier({ providerID: option.providerID, modelID: option.modelID }, option.title);
59
- };
60
-
61
- const applyNativeProviderModelAndTrack = async (providerID: string) => {
62
- const applied = await applyNativeProviderModel(providerID);
63
- if (applied) nativeProviderID = providerID;
64
- return applied;
65
- };
66
-
67
- const selectedAccountBarSync = createSelectedAccountBarSync({
68
- dialogOpen: () => api.ui.dialog.open,
69
- selectedProvider: () => (getBalancingEnabled(state.db) ? undefined : getSelectedAccount(state.db)?.providerID),
70
- currentProvider: () => nativeProviderID ?? sessionProviderID,
71
- applyProvider: applyNativeProviderModelAndTrack,
72
- });
73
-
74
- api.lifecycle.onDispose(() => {
75
- usageAutoRefresh.dispose();
76
- state.dispose();
77
- });
78
-
79
- const openDashboard = () => {
80
- if (api.route.current.name !== "balancer.dashboard") dashboardReturnRoute = copyRoute(api.route.current);
81
- api.route.navigate("balancer.dashboard");
82
- };
83
-
84
- const openPriority = () => {
85
- api.route.navigate("balancer.priority");
86
- };
87
-
88
- const backFromDashboard = () => {
89
- const route = dashboardReturnRoute;
90
- dashboardReturnRoute = undefined;
91
- if (route) api.route.navigate(route.name, "params" in route ? route.params : undefined);
92
- else api.route.navigate("home");
93
- };
94
-
95
- const unregisterDashboard = api.route.register([
96
- {
97
- name: "balancer.dashboard",
98
- render: () =>
99
- createComponent(dashboardModule.Dashboard, {
100
- api,
101
- state,
102
- onBack: backFromDashboard,
103
- openPriority,
104
- openConnect: () => openNativeConnect({ ...api, db: state.db }),
105
- renameAccount: (providerID, alias) => renameDialogModule.openRenameDialog(api, state, providerID, alias),
106
- removeAccount: (providerID, alias) => removeAccountFromTui(api, state, providerID, alias),
107
- }),
108
- },
109
- {
110
- name: "balancer.priority",
111
- render: () =>
112
- createComponent(priorityScreenModule.PriorityScreen, {
113
- api,
114
- state,
115
- onBack: () => api.route.navigate("balancer.dashboard"),
116
- openModelPicker: (providerID, onComplete) =>
117
- providerModelDialogModule.openProviderModelDialog(api, state, providerID, {
118
- applyNativeSelection: false,
119
- onSelected: (model) => setProviderModel(state.db, model.providerID, model.modelID),
120
- onComplete,
121
- }),
122
- }),
123
- },
124
- ]);
125
- api.lifecycle.onDispose(unregisterDashboard);
126
-
127
- const unregisterKeymap = api.keymap.registerLayer({
128
- commands: [
129
- {
130
- name: "balancer.dashboard.open",
131
- title: "Open Balancer Dashboard",
132
- category: "Plugin",
133
- namespace: "palette",
134
- slashName: "balancer",
135
- run() {
136
- openDashboard();
137
- },
138
- },
139
- ],
140
- bindings: [{ key: "ctrl+b", cmd: "balancer.dashboard.open" }],
141
- });
142
- api.lifecycle.onDispose(unregisterKeymap);
143
-
144
- api.slots.register({
145
- slots: {
146
- session_prompt_right(_ctx, value) {
147
- sessionProviderID = inferProviderID(api.state.session.get(value.session_id));
148
- void usageAutoRefresh.refreshForPrompt();
149
- void selectedAccountBarSync.maybeSync();
150
- void balancerBarSync.maybeSync();
151
- return createComponent(statusIndicatorModule.BalancerStatusIndicator, {
152
- api,
153
- state,
154
- providerID: () => inferProviderID(api.state.session.get(value.session_id)),
155
- });
156
- },
157
- sidebar_content(_ctx, value) {
158
- return createComponent(sidebarModule.BalancerSidebar, {
159
- api,
160
- state,
161
- openDashboard,
162
- activateAccount: (providerID, alias) => {
163
- return activateAccount(api, state, providerID, alias, {
164
- sessionProviderID: nativeProviderID ?? inferProviderID(api.state.session.get(value.session_id)),
165
- applyNativeProviderModel: applyNativeProviderModelAndTrack,
166
- });
167
- },
168
- });
169
- },
170
- },
171
- });
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
+ });
172
222
  };
173
223
 
174
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.8",
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.1"
54
63
  }
@@ -1,14 +0,0 @@
1
- import type { Database } from "bun:sqlite";
2
- import type { Account, AuthInfo, SelectedModel } from "./types";
3
- export declare function normalizeAlias(alias: string): string;
4
- export declare function saveAccount(db: Database, providerID: string, aliasInput: string, auth: AuthInfo): Account;
5
- export declare function updateAccountAuth(db: Database, providerID: string, aliasInput: string, auth: AuthInfo): Account | undefined;
6
- export declare function renameAccount(db: Database, providerID: string, fromAliasInput: string, toAliasInput: string): Account;
7
- export declare function getAccount(db: Database, providerID: string, alias: string): Account | undefined;
8
- export declare function listAccounts(db: Database, providerID?: string): Account[];
9
- export declare function setActiveAccount(db: Database, providerID: string, alias: string): Account;
10
- export declare function removeAccount(db: Database, providerID: string, alias: string): boolean;
11
- export declare function getActiveAccount(db: Database, providerID: string): Account | undefined;
12
- export declare function getSelectedAccount(db: Database): Account | undefined;
13
- export declare function setSelectedModel(db: Database, providerID: string, modelID: string): SelectedModel;
14
- export declare function getSelectedModel(db: Database, providerID: string): SelectedModel | undefined;