@thelioo/opencode-balancer 0.1.0 → 0.1.4

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 (167) hide show
  1. package/INSTALL.txt +16 -7
  2. package/README.md +24 -45
  3. package/dist/core/accounts.d.ts +14 -0
  4. package/dist/core/accounts.js +260 -0
  5. package/dist/core/accounts.js.map +1 -0
  6. package/dist/core/database.d.ts +4 -0
  7. package/dist/core/database.js +69 -0
  8. package/dist/core/database.js.map +1 -0
  9. package/dist/core/events.d.ts +18 -0
  10. package/dist/core/events.js +39 -0
  11. package/dist/core/events.js.map +1 -0
  12. package/dist/core/native-auth-suppression.d.ts +3 -0
  13. package/dist/core/native-auth-suppression.js +19 -0
  14. package/dist/core/native-auth-suppression.js.map +1 -0
  15. package/dist/core/native-connect.d.ts +4 -0
  16. package/dist/core/native-connect.js +19 -0
  17. package/dist/core/native-connect.js.map +1 -0
  18. package/dist/core/path.d.ts +4 -0
  19. package/dist/core/path.js +26 -0
  20. package/dist/core/path.js.map +1 -0
  21. package/dist/core/pending.d.ts +9 -0
  22. package/dist/core/pending.js +237 -0
  23. package/dist/core/pending.js.map +1 -0
  24. package/dist/core/priority.d.ts +20 -0
  25. package/dist/core/priority.js +120 -0
  26. package/dist/core/priority.js.map +1 -0
  27. package/dist/core/schema.d.ts +2 -0
  28. package/dist/core/schema.js +265 -0
  29. package/dist/core/schema.js.map +1 -0
  30. package/dist/core/time.d.ts +1 -0
  31. package/dist/core/time.js +4 -0
  32. package/dist/core/time.js.map +1 -0
  33. package/dist/core/types.d.ts +59 -0
  34. package/dist/core/types.js +2 -0
  35. package/dist/core/types.js.map +1 -0
  36. package/dist/core/usage/index.d.ts +4 -0
  37. package/dist/core/usage/index.js +16 -0
  38. package/dist/core/usage/index.js.map +1 -0
  39. package/dist/core/usage/providers/copilot.d.ts +2 -0
  40. package/dist/core/usage/providers/copilot.js +169 -0
  41. package/dist/core/usage/providers/copilot.js.map +1 -0
  42. package/dist/core/usage/providers/openai.d.ts +2 -0
  43. package/dist/core/usage/providers/openai.js +133 -0
  44. package/dist/core/usage/providers/openai.js.map +1 -0
  45. package/dist/core/usage/redact.d.ts +3 -0
  46. package/dist/core/usage/redact.js +67 -0
  47. package/dist/core/usage/redact.js.map +1 -0
  48. package/dist/core/usage/store.d.ts +4 -0
  49. package/dist/core/usage/store.js +31 -0
  50. package/dist/core/usage/store.js.map +1 -0
  51. package/dist/core/usage/types.d.ts +21 -0
  52. package/dist/core/usage/types.js +2 -0
  53. package/dist/core/usage/types.js.map +1 -0
  54. package/dist/index.d.ts +1 -37
  55. package/dist/index.js +1 -60
  56. package/dist/index.js.map +1 -1
  57. package/dist/server/auth-watcher.d.ts +31 -0
  58. package/dist/server/auth-watcher.js +227 -0
  59. package/dist/server/auth-watcher.js.map +1 -0
  60. package/dist/server/commands.d.ts +2 -0
  61. package/dist/server/commands.js +46 -0
  62. package/dist/server/commands.js.map +1 -0
  63. package/dist/server/fetch-patch.d.ts +3 -0
  64. package/dist/server/fetch-patch.js +118 -0
  65. package/dist/server/fetch-patch.js.map +1 -0
  66. package/dist/server/index.d.ts +8 -0
  67. package/dist/server/index.js +94 -0
  68. package/dist/server/index.js.map +1 -0
  69. package/dist/server/native.d.ts +6 -0
  70. package/dist/server/native.js +35 -0
  71. package/dist/server/native.js.map +1 -0
  72. package/dist/server/request-balancer.d.ts +16 -0
  73. package/dist/server/request-balancer.js +43 -0
  74. package/dist/server/request-balancer.js.map +1 -0
  75. package/dist/tui/actions.d.ts +41 -0
  76. package/dist/tui/actions.js +88 -0
  77. package/dist/tui/actions.js.map +1 -0
  78. package/dist/tui/actions.ts +140 -0
  79. package/dist/tui/balancer-bar-sync.d.ts +19 -0
  80. package/dist/tui/balancer-bar-sync.js +45 -0
  81. package/dist/tui/balancer-bar-sync.js.map +1 -0
  82. package/dist/tui/balancer-bar-sync.ts +56 -0
  83. package/dist/tui/components/alias-dialog.d.ts +4 -0
  84. package/dist/tui/components/alias-dialog.js +57 -0
  85. package/dist/tui/components/alias-dialog.js.map +1 -0
  86. package/dist/tui/components/alias-dialog.tsx +72 -0
  87. package/dist/tui/components/dashboard.d.ts +12 -0
  88. package/dist/tui/components/dashboard.js +201 -0
  89. package/dist/tui/components/dashboard.js.map +1 -0
  90. package/dist/tui/components/dashboard.tsx +393 -0
  91. package/dist/tui/components/priority-screen.d.ts +9 -0
  92. package/dist/tui/components/priority-screen.js +120 -0
  93. package/dist/tui/components/priority-screen.js.map +1 -0
  94. package/dist/tui/components/priority-screen.tsx +302 -0
  95. package/dist/tui/components/provider-model-dialog.d.ts +13 -0
  96. package/dist/tui/components/provider-model-dialog.js +45 -0
  97. package/dist/tui/components/provider-model-dialog.js.map +1 -0
  98. package/dist/tui/components/provider-model-dialog.tsx +71 -0
  99. package/dist/tui/components/rename-dialog.d.ts +4 -0
  100. package/dist/tui/components/rename-dialog.js +24 -0
  101. package/dist/tui/components/rename-dialog.js.map +1 -0
  102. package/dist/tui/components/rename-dialog.tsx +40 -0
  103. package/dist/tui/components/sidebar.d.ts +10 -0
  104. package/dist/tui/components/sidebar.js +27 -0
  105. package/dist/tui/components/sidebar.js.map +1 -0
  106. package/dist/tui/components/sidebar.tsx +97 -0
  107. package/dist/tui/components/status-indicator.d.ts +9 -0
  108. package/dist/tui/components/status-indicator.js +59 -0
  109. package/dist/tui/components/status-indicator.js.map +1 -0
  110. package/dist/tui/components/status-indicator.tsx +78 -0
  111. package/dist/tui/components/usage-bar.d.ts +8 -0
  112. package/dist/tui/components/usage-bar.js +7 -0
  113. package/dist/tui/components/usage-bar.js.map +1 -0
  114. package/dist/tui/components/usage-bar.tsx +13 -0
  115. package/dist/tui/components/usage-display.d.ts +10 -0
  116. package/dist/tui/components/usage-display.js +32 -0
  117. package/dist/tui/components/usage-display.js.map +1 -0
  118. package/dist/tui/components/usage-display.tsx +29 -0
  119. package/dist/tui/connect.d.ts +30 -0
  120. package/dist/tui/connect.js +73 -0
  121. package/dist/tui/connect.js.map +1 -0
  122. package/dist/tui/connect.ts +100 -0
  123. package/dist/tui/dashboard-keys.d.ts +45 -0
  124. package/dist/tui/dashboard-keys.js +44 -0
  125. package/dist/tui/dashboard-keys.js.map +1 -0
  126. package/dist/tui/dashboard-keys.ts +60 -0
  127. package/dist/tui/native-model-apply.d.ts +21 -0
  128. package/dist/tui/native-model-apply.js +53 -0
  129. package/dist/tui/native-model-apply.js.map +1 -0
  130. package/dist/tui/native-model-apply.ts +73 -0
  131. package/dist/tui/priority-keys.d.ts +40 -0
  132. package/dist/tui/priority-keys.js +38 -0
  133. package/dist/tui/priority-keys.js.map +1 -0
  134. package/dist/tui/priority-keys.ts +59 -0
  135. package/dist/tui/provider-models.d.ts +19 -0
  136. package/dist/tui/provider-models.js +17 -0
  137. package/dist/tui/provider-models.js.map +1 -0
  138. package/dist/tui/provider-models.ts +36 -0
  139. package/dist/tui/responsive.d.ts +9 -0
  140. package/dist/tui/responsive.js +13 -0
  141. package/dist/tui/responsive.js.map +1 -0
  142. package/dist/tui/responsive.ts +16 -0
  143. package/dist/tui/selection-colors.d.ts +10 -0
  144. package/dist/tui/selection-colors.js +38 -0
  145. package/dist/tui/selection-colors.js.map +1 -0
  146. package/dist/tui/selection-colors.ts +45 -0
  147. package/dist/tui/state.d.ts +14 -0
  148. package/dist/tui/state.js +46 -0
  149. package/dist/tui/state.js.map +1 -0
  150. package/dist/tui/state.ts +65 -0
  151. package/dist/tui/status-format.d.ts +15 -0
  152. package/dist/tui/status-format.js +17 -0
  153. package/dist/tui/status-format.js.map +1 -0
  154. package/dist/tui/status-format.ts +29 -0
  155. package/dist/tui/tui.d.ts +7 -0
  156. package/dist/tui/tui.js +120 -0
  157. package/dist/tui/tui.js.map +1 -0
  158. package/dist/tui/tui.tsx +142 -0
  159. package/dist/tui/usage-auto-refresh.d.ts +16 -0
  160. package/dist/tui/usage-auto-refresh.js +46 -0
  161. package/dist/tui/usage-auto-refresh.js.map +1 -0
  162. package/dist/tui/usage-auto-refresh.ts +64 -0
  163. package/dist/tui/usage-format.d.ts +2 -0
  164. package/dist/tui/usage-format.js +17 -0
  165. package/dist/tui/usage-format.js.map +1 -0
  166. package/dist/tui/usage-format.ts +14 -0
  167. package/package.json +18 -3
@@ -0,0 +1,43 @@
1
+ import { getAccount, listAccounts, normalizeAlias } from "../core/accounts";
2
+ import { now } from "../core/time";
3
+ export const INTERNAL_REQUEST_HEADER = "x-opencode-balancer-request";
4
+ export const BALANCER_METADATA_KEY = "opencodeBalancerCommand";
5
+ export const RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504, 529]);
6
+ const pendingRequests = new Map();
7
+ export function setPendingRequest(requestID, request) {
8
+ pendingRequests.set(requestID, request);
9
+ }
10
+ export function takePendingRequest(requestID) {
11
+ const request = pendingRequests.get(requestID);
12
+ pendingRequests.delete(requestID);
13
+ return request;
14
+ }
15
+ export function __testGetPendingRequest(requestID) {
16
+ return pendingRequests.get(requestID);
17
+ }
18
+ export function __testClearPendingRequests() {
19
+ pendingRequests.clear();
20
+ }
21
+ export function markRateLimited(db, providerID, alias, retryAfterMs = 60_000) {
22
+ const account = getAccount(db, providerID, alias);
23
+ if (!account)
24
+ return;
25
+ const timestamp = now();
26
+ db.query(`UPDATE accounts
27
+ SET failures = failures + 1,
28
+ rate_limited_until = ?,
29
+ updated_at = ?
30
+ WHERE provider_id = ? AND alias = ?`).run(timestamp + retryAfterMs, timestamp, providerID, account.alias);
31
+ }
32
+ export function chooseFailoverAccount(db, providerID, currentAlias) {
33
+ const timestamp = now();
34
+ const normalizedCurrentAlias = normalizeAlias(currentAlias);
35
+ return listAccounts(db, providerID).find((account) => {
36
+ if (account.alias === normalizedCurrentAlias)
37
+ return false;
38
+ if (account.disabled)
39
+ return false;
40
+ return !account.rateLimitedUntil || account.rateLimitedUntil <= timestamp;
41
+ });
42
+ }
43
+ //# sourceMappingURL=request-balancer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-balancer.js","sourceRoot":"","sources":["../../src/server/request-balancer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAGnC,MAAM,CAAC,MAAM,uBAAuB,GAAG,6BAA6B,CAAC;AACrE,MAAM,CAAC,MAAM,qBAAqB,GAAG,yBAAyB,CAAC;AAE/D,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAOxE,MAAM,eAAe,GAAG,IAAI,GAAG,EAA0B,CAAC;AAE1D,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,OAAuB;IACxE,eAAe,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAChD,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC/C,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAClC,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,SAAiB;IACrD,OAAO,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,0BAA0B;IACtC,eAAe,CAAC,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,eAAe,CAC3B,EAAY,EACZ,UAAkB,EAClB,KAAa,EACb,YAAY,GAAG,MAAM;IAErB,MAAM,OAAO,GAAG,UAAU,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;IAClD,IAAI,CAAC,OAAO;QAAE,OAAO;IAErB,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC;IACxB,EAAE,CAAC,KAAK,CACJ;;;;6CAIqC,CACxC,CAAC,GAAG,CAAC,SAAS,GAAG,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,qBAAqB,CACjC,EAAY,EACZ,UAAkB,EAClB,YAAoB;IAEpB,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC;IACxB,MAAM,sBAAsB,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;IAC5D,OAAO,YAAY,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;QACjD,IAAI,OAAO,CAAC,KAAK,KAAK,sBAAsB;YAAE,OAAO,KAAK,CAAC;QAC3D,IAAI,OAAO,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QACnC,OAAO,CAAC,OAAO,CAAC,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,IAAI,SAAS,CAAC;IAC9E,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { refreshAccountUsage } from "../core/usage";
2
+ import type { BalancerTuiState } from "./state";
3
+ type AuthSetApi = {
4
+ client: {
5
+ auth: {
6
+ set: (input: any) => unknown;
7
+ };
8
+ };
9
+ keymap?: {
10
+ dispatchCommand?: (command: string) => unknown;
11
+ };
12
+ ui?: {
13
+ toast: (input: {
14
+ variant: "success" | "warning" | "error" | "info";
15
+ message: string;
16
+ }) => unknown;
17
+ };
18
+ };
19
+ type ActivateAccountOptions = {
20
+ sessionProviderID?: string;
21
+ openProviderModelPicker?: (providerID: string) => void;
22
+ };
23
+ type ToastApi = {
24
+ ui: {
25
+ toast: (input: {
26
+ variant: "success" | "warning" | "error" | "info";
27
+ message: string;
28
+ }) => unknown;
29
+ };
30
+ };
31
+ type RefreshUsageOptions = {
32
+ refreshUsage?: typeof refreshAccountUsage;
33
+ silent?: boolean;
34
+ };
35
+ export declare function savePendingAlias(state: BalancerTuiState, pendingID: string, alias: string): import("../core/types").Account;
36
+ export declare function activateAccount(api: AuthSetApi, state: BalancerTuiState, providerID: string, alias: string, options?: ActivateAccountOptions): Promise<void>;
37
+ export declare function refreshUsageForAccount(api: ToastApi, state: BalancerTuiState, providerID: string, alias: string, options?: RefreshUsageOptions): Promise<void>;
38
+ export declare function removeAccountFromTui(api: ToastApi, state: BalancerTuiState, providerID: string, alias: string): void;
39
+ export declare function renameAccountFromTui(api: ToastApi, state: BalancerTuiState, providerID: string, alias: string, nextAlias: string): import("../core/types").Account;
40
+ export declare function removePendingFromTui(api: ToastApi, state: BalancerTuiState, pendingID: string): void;
41
+ export {};
@@ -0,0 +1,88 @@
1
+ import { getAccount, getActiveAccount, removeAccount, renameAccount, setActiveAccount } from "../core/accounts";
2
+ import { appendEvent } from "../core/events";
3
+ import { completePendingConnection, removePendingConnection } from "../core/pending";
4
+ import { refreshAccountUsage } from "../core/usage";
5
+ import { saveUsageSnapshot } from "../core/usage/store";
6
+ import { suppressNativeAuthCapture } from "../core/native-auth-suppression";
7
+ export function savePendingAlias(state, pendingID, alias) {
8
+ const account = completePendingConnection(state.db, pendingID, alias);
9
+ state.refresh();
10
+ return account;
11
+ }
12
+ export async function activateAccount(api, state, providerID, alias, options = {}) {
13
+ const account = setActiveAccount(state.db, providerID, alias) ?? getActiveAccount(state.db, providerID);
14
+ if (account) {
15
+ try {
16
+ suppressNativeAuthCapture(state.db, providerID);
17
+ await api.client.auth.set({ path: { id: providerID }, body: account.auth });
18
+ }
19
+ catch { }
20
+ }
21
+ state.refresh();
22
+ api.ui?.toast({ variant: "success", message: `Activated ${providerID}/${alias}.` });
23
+ options.openProviderModelPicker?.(providerID);
24
+ }
25
+ export async function refreshUsageForAccount(api, state, providerID, alias, options = {}) {
26
+ const account = getAccount(state.db, providerID, alias) ?? state.accounts().find((candidate) => {
27
+ return candidate.providerID === providerID && candidate.alias === alias;
28
+ });
29
+ if (!account) {
30
+ api.ui.toast({ variant: "error", message: `Account not found: ${providerID}/${alias}` });
31
+ return;
32
+ }
33
+ try {
34
+ const snapshot = await (options.refreshUsage ?? refreshAccountUsage)(account);
35
+ saveUsageSnapshot(state.db, snapshot);
36
+ appendEvent(state.db, {
37
+ type: snapshot.confidence === "unavailable" ? "usage_unavailable" : "usage_refreshed",
38
+ providerID,
39
+ alias,
40
+ message: snapshot.message,
41
+ });
42
+ state.refresh();
43
+ if (!options.silent) {
44
+ api.ui.toast({
45
+ variant: snapshot.confidence === "unavailable" ? "warning" : "success",
46
+ message: snapshot.message,
47
+ });
48
+ }
49
+ }
50
+ catch (error) {
51
+ const message = error instanceof Error ? error.message : "Usage refresh failed.";
52
+ api.ui.toast({ variant: "error", message });
53
+ }
54
+ }
55
+ export function removeAccountFromTui(api, state, providerID, alias) {
56
+ const removed = removeAccount(state.db, providerID, alias);
57
+ if (!removed) {
58
+ api.ui.toast({ variant: "error", message: `Account not found: ${providerID}/${alias}` });
59
+ return;
60
+ }
61
+ const message = `Removed account ${providerID}/${alias}.`;
62
+ appendEvent(state.db, {
63
+ type: "account_removed",
64
+ providerID,
65
+ alias,
66
+ message,
67
+ });
68
+ state.refresh();
69
+ state.removeAccountView(providerID, alias);
70
+ api.ui.toast({ variant: "success", message });
71
+ }
72
+ export function renameAccountFromTui(api, state, providerID, alias, nextAlias) {
73
+ const account = renameAccount(state.db, providerID, alias, nextAlias);
74
+ state.refresh();
75
+ api.ui.toast({ variant: "success", message: `Renamed ${providerID}/${alias} to ${account.alias}.` });
76
+ return account;
77
+ }
78
+ export function removePendingFromTui(api, state, pendingID) {
79
+ const removed = removePendingConnection(state.db, pendingID);
80
+ if (!removed) {
81
+ api.ui.toast({ variant: "error", message: "Pending connection not found." });
82
+ return;
83
+ }
84
+ state.refresh();
85
+ state.removePendingView(pendingID);
86
+ api.ui.toast({ variant: "success", message: "Removed pending connection." });
87
+ }
88
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/tui/actions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ import { getAccount, getActiveAccount, removeAccount, renameAccount, setActiveAccount } from "../core/accounts";
2
+ import { appendEvent } from "../core/events";
3
+ import { completePendingConnection, removePendingConnection } from "../core/pending";
4
+ import { refreshAccountUsage } from "../core/usage";
5
+ import { saveUsageSnapshot } from "../core/usage/store";
6
+ import { suppressNativeAuthCapture } from "../core/native-auth-suppression";
7
+ import type { BalancerTuiState } from "./state";
8
+
9
+ type AuthSetApi = {
10
+ client: {
11
+ auth: {
12
+ set: (input: any) => unknown;
13
+ };
14
+ };
15
+ keymap?: {
16
+ dispatchCommand?: (command: string) => unknown;
17
+ };
18
+ ui?: {
19
+ toast: (input: { variant: "success" | "warning" | "error" | "info"; message: string }) => unknown;
20
+ };
21
+ };
22
+
23
+ type ActivateAccountOptions = {
24
+ sessionProviderID?: string;
25
+ openProviderModelPicker?: (providerID: string) => void;
26
+ };
27
+
28
+ type ToastApi = {
29
+ ui: {
30
+ toast: (input: { variant: "success" | "warning" | "error" | "info"; message: string }) => unknown;
31
+ };
32
+ };
33
+
34
+ type RefreshUsageOptions = {
35
+ refreshUsage?: typeof refreshAccountUsage;
36
+ silent?: boolean;
37
+ };
38
+
39
+ export function savePendingAlias(state: BalancerTuiState, pendingID: string, alias: string) {
40
+ const account = completePendingConnection(state.db, pendingID, alias);
41
+ state.refresh();
42
+ return account;
43
+ }
44
+
45
+ export async function activateAccount(
46
+ api: AuthSetApi,
47
+ state: BalancerTuiState,
48
+ providerID: string,
49
+ alias: string,
50
+ options: ActivateAccountOptions = {},
51
+ ) {
52
+ const account = setActiveAccount(state.db, providerID, alias) ?? getActiveAccount(state.db, providerID);
53
+
54
+ if (account) {
55
+ try {
56
+ suppressNativeAuthCapture(state.db, providerID);
57
+ await api.client.auth.set({ path: { id: providerID }, body: account.auth });
58
+ } catch {}
59
+ }
60
+
61
+ state.refresh();
62
+ api.ui?.toast({ variant: "success", message: `Activated ${providerID}/${alias}.` });
63
+ options.openProviderModelPicker?.(providerID);
64
+ }
65
+
66
+ export async function refreshUsageForAccount(
67
+ api: ToastApi,
68
+ state: BalancerTuiState,
69
+ providerID: string,
70
+ alias: string,
71
+ options: RefreshUsageOptions = {},
72
+ ) {
73
+ const account = getAccount(state.db, providerID, alias) ?? state.accounts().find((candidate) => {
74
+ return candidate.providerID === providerID && candidate.alias === alias;
75
+ });
76
+
77
+ if (!account) {
78
+ api.ui.toast({ variant: "error", message: `Account not found: ${providerID}/${alias}` });
79
+ return;
80
+ }
81
+
82
+ try {
83
+ const snapshot = await (options.refreshUsage ?? refreshAccountUsage)(account);
84
+ saveUsageSnapshot(state.db, snapshot);
85
+ appendEvent(state.db, {
86
+ type: snapshot.confidence === "unavailable" ? "usage_unavailable" : "usage_refreshed",
87
+ providerID,
88
+ alias,
89
+ message: snapshot.message,
90
+ });
91
+ state.refresh();
92
+ if (!options.silent) {
93
+ api.ui.toast({
94
+ variant: snapshot.confidence === "unavailable" ? "warning" : "success",
95
+ message: snapshot.message,
96
+ });
97
+ }
98
+ } catch (error) {
99
+ const message = error instanceof Error ? error.message : "Usage refresh failed.";
100
+ api.ui.toast({ variant: "error", message });
101
+ }
102
+ }
103
+
104
+ export function removeAccountFromTui(api: ToastApi, state: BalancerTuiState, providerID: string, alias: string) {
105
+ const removed = removeAccount(state.db, providerID, alias);
106
+ if (!removed) {
107
+ api.ui.toast({ variant: "error", message: `Account not found: ${providerID}/${alias}` });
108
+ return;
109
+ }
110
+
111
+ const message = `Removed account ${providerID}/${alias}.`;
112
+ appendEvent(state.db, {
113
+ type: "account_removed",
114
+ providerID,
115
+ alias,
116
+ message,
117
+ });
118
+ state.refresh();
119
+ state.removeAccountView(providerID, alias);
120
+ api.ui.toast({ variant: "success", message });
121
+ }
122
+
123
+ export function renameAccountFromTui(api: ToastApi, state: BalancerTuiState, providerID: string, alias: string, nextAlias: string) {
124
+ const account = renameAccount(state.db, providerID, alias, nextAlias);
125
+ state.refresh();
126
+ api.ui.toast({ variant: "success", message: `Renamed ${providerID}/${alias} to ${account.alias}.` });
127
+ return account;
128
+ }
129
+
130
+ export function removePendingFromTui(api: ToastApi, state: BalancerTuiState, pendingID: string) {
131
+ const removed = removePendingConnection(state.db, pendingID);
132
+ if (!removed) {
133
+ api.ui.toast({ variant: "error", message: "Pending connection not found." });
134
+ return;
135
+ }
136
+
137
+ state.refresh();
138
+ state.removePendingView(pendingID);
139
+ api.ui.toast({ variant: "success", message: "Removed pending connection." });
140
+ }
@@ -0,0 +1,19 @@
1
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
2
+ import type { BalancerTuiState } from "./state";
3
+ import { type NativeModelApplier } from "./native-model-apply";
4
+ export type BalancerBarSyncDeps = {
5
+ balancingEnabled: () => boolean;
6
+ dialogOpen: () => boolean;
7
+ activeSelection: () => {
8
+ providerID: string;
9
+ modelID: string;
10
+ } | undefined;
11
+ modelTitle: (providerID: string, modelID: string) => string | undefined;
12
+ apply: NativeModelApplier;
13
+ };
14
+ export declare function createBalancerBarSync(deps: BalancerBarSyncDeps): {
15
+ maybeSync: () => Promise<boolean>;
16
+ };
17
+ export declare function createTuiBalancerBarSync(api: TuiPluginApi, state: BalancerTuiState): {
18
+ maybeSync: () => Promise<boolean>;
19
+ };
@@ -0,0 +1,45 @@
1
+ import { getBalancingEnabled, resolveActiveSelection } from "../core/priority";
2
+ import { createNativeModelApplier } from "./native-model-apply";
3
+ export function createBalancerBarSync(deps) {
4
+ let lastApplied;
5
+ let applying = false;
6
+ const maybeSync = async () => {
7
+ if (!deps.balancingEnabled()) {
8
+ lastApplied = undefined;
9
+ return false;
10
+ }
11
+ if (applying || deps.dialogOpen())
12
+ return false;
13
+ const selection = deps.activeSelection();
14
+ if (!selection)
15
+ return false;
16
+ const key = `${selection.providerID}/${selection.modelID}`;
17
+ if (key === lastApplied)
18
+ return false;
19
+ const title = deps.modelTitle(selection.providerID, selection.modelID) ?? selection.modelID;
20
+ applying = true;
21
+ try {
22
+ const applied = await deps.apply(selection, title);
23
+ if (applied)
24
+ lastApplied = key;
25
+ return applied;
26
+ }
27
+ finally {
28
+ applying = false;
29
+ }
30
+ };
31
+ return { maybeSync };
32
+ }
33
+ export function createTuiBalancerBarSync(api, state) {
34
+ return createBalancerBarSync({
35
+ balancingEnabled: () => getBalancingEnabled(state.db),
36
+ dialogOpen: () => api.ui.dialog.open,
37
+ activeSelection: () => resolveActiveSelection(state.db),
38
+ modelTitle: (providerID, modelID) => {
39
+ const provider = api.state.provider.find((item) => item.id === providerID);
40
+ return provider?.models?.[modelID]?.name;
41
+ },
42
+ apply: createNativeModelApplier(api),
43
+ });
44
+ }
45
+ //# sourceMappingURL=balancer-bar-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"balancer-bar-sync.js","sourceRoot":"","sources":["../../src/tui/balancer-bar-sync.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE/E,OAAO,EAAE,wBAAwB,EAA2B,MAAM,sBAAsB,CAAC;AAUzF,MAAM,UAAU,qBAAqB,CAAC,IAAyB;IAC3D,IAAI,WAA+B,CAAC;IACpC,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,MAAM,SAAS,GAAG,KAAK,IAAI,EAAE;QACzB,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC;YAC3B,WAAW,GAAG,SAAS,CAAC;YACxB,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,IAAI,QAAQ,IAAI,IAAI,CAAC,UAAU,EAAE;YAAE,OAAO,KAAK,CAAC;QAEhD,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACzC,IAAI,CAAC,SAAS;YAAE,OAAO,KAAK,CAAC;QAE7B,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC,UAAU,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;QAC3D,IAAI,GAAG,KAAK,WAAW;YAAE,OAAO,KAAK,CAAC;QAEtC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,OAAO,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC;QAC5F,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YACnD,IAAI,OAAO;gBAAE,WAAW,GAAG,GAAG,CAAC;YAC/B,OAAO,OAAO,CAAC;QACnB,CAAC;gBAAS,CAAC;YACP,QAAQ,GAAG,KAAK,CAAC;QACrB,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,EAAE,SAAS,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,GAAiB,EAAE,KAAuB;IAC/E,OAAO,qBAAqB,CAAC;QACzB,gBAAgB,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;QACrD,UAAU,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI;QACpC,eAAe,EAAE,GAAG,EAAE,CAAC,sBAAsB,CAAC,KAAK,CAAC,EAAE,CAAC;QACvD,UAAU,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE;YAChC,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC;YAC3E,OAAO,QAAQ,EAAE,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC;QAC7C,CAAC;QACD,KAAK,EAAE,wBAAwB,CAAC,GAAG,CAAC;KACvC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,56 @@
1
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
2
+ import { getBalancingEnabled, resolveActiveSelection } from "../core/priority";
3
+ import type { BalancerTuiState } from "./state";
4
+ import { createNativeModelApplier, type NativeModelApplier } from "./native-model-apply";
5
+
6
+ export type BalancerBarSyncDeps = {
7
+ balancingEnabled: () => boolean;
8
+ dialogOpen: () => boolean;
9
+ activeSelection: () => { providerID: string; modelID: string } | undefined;
10
+ modelTitle: (providerID: string, modelID: string) => string | undefined;
11
+ apply: NativeModelApplier;
12
+ };
13
+
14
+ export function createBalancerBarSync(deps: BalancerBarSyncDeps) {
15
+ let lastApplied: string | undefined;
16
+ let applying = false;
17
+
18
+ const maybeSync = async () => {
19
+ if (!deps.balancingEnabled()) {
20
+ lastApplied = undefined;
21
+ return false;
22
+ }
23
+ if (applying || deps.dialogOpen()) return false;
24
+
25
+ const selection = deps.activeSelection();
26
+ if (!selection) return false;
27
+
28
+ const key = `${selection.providerID}/${selection.modelID}`;
29
+ if (key === lastApplied) return false;
30
+
31
+ const title = deps.modelTitle(selection.providerID, selection.modelID) ?? selection.modelID;
32
+ applying = true;
33
+ try {
34
+ const applied = await deps.apply(selection, title);
35
+ if (applied) lastApplied = key;
36
+ return applied;
37
+ } finally {
38
+ applying = false;
39
+ }
40
+ };
41
+
42
+ return { maybeSync };
43
+ }
44
+
45
+ export function createTuiBalancerBarSync(api: TuiPluginApi, state: BalancerTuiState) {
46
+ return createBalancerBarSync({
47
+ balancingEnabled: () => getBalancingEnabled(state.db),
48
+ dialogOpen: () => api.ui.dialog.open,
49
+ activeSelection: () => resolveActiveSelection(state.db),
50
+ modelTitle: (providerID, modelID) => {
51
+ const provider = api.state.provider.find((item) => item.id === providerID);
52
+ return provider?.models?.[modelID]?.name;
53
+ },
54
+ apply: createNativeModelApplier(api),
55
+ });
56
+ }
@@ -0,0 +1,4 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
3
+ import type { BalancerTuiState } from "../state";
4
+ export declare function openAliasDialog(api: TuiPluginApi, state: BalancerTuiState, pendingID: string): boolean;
@@ -0,0 +1,57 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "@opentui/solid/jsx-runtime";
2
+ import { normalizeAlias } from "../../core/accounts";
3
+ import { claimPendingPrompt, pendingPromptGroupID, releasePendingPrompt } from "../../core/pending";
4
+ import { savePendingAlias } from "../actions";
5
+ function errorMessage(error) {
6
+ return error instanceof Error && error.message ? error.message : "Failed to save alias";
7
+ }
8
+ const openPendingDialogs = new Set();
9
+ export function openAliasDialog(api, state, pendingID) {
10
+ const dialogID = pendingPromptGroupID(state.db, pendingID) ?? pendingID;
11
+ if (openPendingDialogs.has(dialogID)) {
12
+ if ("open" in api.ui.dialog && !api.ui.dialog.open)
13
+ openPendingDialogs.delete(dialogID);
14
+ else
15
+ return false;
16
+ }
17
+ let claimed = claimPendingPrompt(state.db, pendingID);
18
+ if (!claimed) {
19
+ releasePendingPrompt(state.db, pendingID);
20
+ claimed = claimPendingPrompt(state.db, pendingID);
21
+ }
22
+ if (!claimed)
23
+ return false;
24
+ const pending = state.pending().find((item) => item.id === pendingID) ?? claimed;
25
+ const providerID = pending?.providerID ?? "provider";
26
+ openPendingDialogs.add(dialogID);
27
+ const close = () => {
28
+ openPendingDialogs.delete(dialogID);
29
+ api.ui.dialog.clear();
30
+ };
31
+ try {
32
+ api.ui.dialog.setSize("medium");
33
+ api.ui.dialog.replace(() => (_jsx(api.ui.DialogPrompt, { title: "Save pending connection", placeholder: "account alias", description: () => (_jsxs("text", { fg: api.theme.current.textMuted, wrapMode: "none", children: ["Choose an alias for ", providerID, "/", pending?.authType ?? "auth", "."] })), onCancel: close, onConfirm: async (value) => {
34
+ const alias = normalizeAlias(value);
35
+ if (!alias) {
36
+ api.ui.toast({ variant: "error", message: "Alias is required" });
37
+ return;
38
+ }
39
+ try {
40
+ const account = savePendingAlias(state, pendingID, alias);
41
+ close();
42
+ api.ui.toast({ variant: "success", message: `Saved ${account.providerID}/${account.alias}` });
43
+ }
44
+ catch (error) {
45
+ openPendingDialogs.delete(dialogID);
46
+ api.ui.toast({ variant: "error", message: errorMessage(error) });
47
+ }
48
+ } })));
49
+ return true;
50
+ }
51
+ catch {
52
+ openPendingDialogs.delete(dialogID);
53
+ releasePendingPrompt(state.db, pendingID);
54
+ return false;
55
+ }
56
+ }
57
+ //# sourceMappingURL=alias-dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alias-dialog.js","sourceRoot":"","sources":["../../../src/tui/components/alias-dialog.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+
3
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
4
+ import { normalizeAlias } from "../../core/accounts";
5
+ import { claimPendingPrompt, pendingPromptGroupID, releasePendingPrompt } from "../../core/pending";
6
+ import { savePendingAlias } from "../actions";
7
+ import type { BalancerTuiState } from "../state";
8
+
9
+ function errorMessage(error: unknown) {
10
+ return error instanceof Error && error.message ? error.message : "Failed to save alias";
11
+ }
12
+
13
+ const openPendingDialogs = new Set<string>();
14
+
15
+ export function openAliasDialog(api: TuiPluginApi, state: BalancerTuiState, pendingID: string) {
16
+ const dialogID = pendingPromptGroupID(state.db, pendingID) ?? pendingID;
17
+ if (openPendingDialogs.has(dialogID)) {
18
+ if ("open" in api.ui.dialog && !api.ui.dialog.open) openPendingDialogs.delete(dialogID);
19
+ else return false;
20
+ }
21
+ let claimed = claimPendingPrompt(state.db, pendingID);
22
+ if (!claimed) {
23
+ releasePendingPrompt(state.db, pendingID);
24
+ claimed = claimPendingPrompt(state.db, pendingID);
25
+ }
26
+ if (!claimed) return false;
27
+ const pending = state.pending().find((item) => item.id === pendingID) ?? claimed;
28
+ const providerID = pending?.providerID ?? "provider";
29
+ openPendingDialogs.add(dialogID);
30
+
31
+ const close = () => {
32
+ openPendingDialogs.delete(dialogID);
33
+ api.ui.dialog.clear();
34
+ };
35
+
36
+ try {
37
+ api.ui.dialog.setSize("medium");
38
+ api.ui.dialog.replace(() => (
39
+ <api.ui.DialogPrompt
40
+ title="Save pending connection"
41
+ placeholder="account alias"
42
+ description={() => (
43
+ <text fg={api.theme.current.textMuted} wrapMode="none">
44
+ Choose an alias for {providerID}/{pending?.authType ?? "auth"}.
45
+ </text>
46
+ )}
47
+ onCancel={close}
48
+ onConfirm={async (value) => {
49
+ const alias = normalizeAlias(value);
50
+ if (!alias) {
51
+ api.ui.toast({ variant: "error", message: "Alias is required" });
52
+ return;
53
+ }
54
+
55
+ try {
56
+ const account = savePendingAlias(state, pendingID, alias);
57
+ close();
58
+ api.ui.toast({ variant: "success", message: `Saved ${account.providerID}/${account.alias}` });
59
+ } catch (error) {
60
+ openPendingDialogs.delete(dialogID);
61
+ api.ui.toast({ variant: "error", message: errorMessage(error) });
62
+ }
63
+ }}
64
+ />
65
+ ));
66
+ return true;
67
+ } catch {
68
+ openPendingDialogs.delete(dialogID);
69
+ releasePendingPrompt(state.db, pendingID);
70
+ return false;
71
+ }
72
+ }
@@ -0,0 +1,12 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
3
+ import type { BalancerTuiState } from "../state";
4
+ export declare function Dashboard(props: {
5
+ api: TuiPluginApi;
6
+ state: BalancerTuiState;
7
+ onBack: () => void;
8
+ openPriority: () => void;
9
+ openConnect: () => void;
10
+ renameAccount: (providerID: string, alias: string) => void;
11
+ removeAccount: (providerID: string, alias: string) => void;
12
+ }): any;