@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
package/INSTALL.txt CHANGED
@@ -4,7 +4,7 @@ Goal
4
4
  Install @thelioo/opencode-balancer as an opencode npm plugin without removing or rewriting the user's existing opencode configuration.
5
5
 
6
6
  What this plugin does
7
- opencode-balancer lets opencode users save multiple authenticated accounts per provider, switch active accounts with /balancer commands, and automatically retry with another saved account when the active account is rate-limited.
7
+ opencode-balancer lets opencode users save multiple authenticated accounts per provider, switch active accounts from the Balancer TUI sidebar/dashboard, and automatically retry with another saved account when the active account is rate-limited.
8
8
 
9
9
  Important rules
10
10
  - Do not run npm install for this plugin. opencode installs npm plugins automatically with Bun at startup.
@@ -41,18 +41,27 @@ Expected config shape
41
41
  }
42
42
 
43
43
  After restart
44
- The user can connect and save accounts with:
45
- /connect anthropic
46
- /balancer alias work
44
+ 1. Tell the user to run opencode's native connect flow:
45
+ /connect <provider>
47
46
 
48
- Useful commands
47
+ 2. Tell the user to use the Balancer TUI modal/sidebar to save the detected pending connection with an alias.
48
+
49
+ 3. If the Balancer dashboard is not visible, tell the user to open it from the opencode command palette.
50
+
51
+ 4. Remind the user to restart opencode after any later config changes.
52
+
53
+ Security note
54
+ The plugin stores saved provider credentials locally in balancer.sqlite under the opencode config directory, usually ~/.config/opencode/balancer.sqlite or OPENCODE_CONFIG_DIR/balancer.sqlite. Keep this file private and never commit, paste, or share it.
55
+
56
+ Fallback commands
57
+ /balancer commands are compatibility and troubleshooting fallbacks. Alias creation is handled by the TUI pending-connection flow.
49
58
  /balancer help
50
59
  /balancer list
51
60
  /balancer status
52
61
  /balancer use <provider> <alias>
53
- /balancer remove <provider> <alias>
62
+ /balancer active <provider>
54
63
 
55
64
  Verification
56
65
  - Confirm the opencode config contains @thelioo/opencode-balancer@latest in the plugin array.
57
66
  - Confirm no existing plugin entries were removed.
58
- - Remind the user that opencode must be restarted before /balancer becomes available.
67
+ - Remind the user that opencode must be restarted before the plugin UI and fallback commands become available.
package/README.md CHANGED
@@ -9,13 +9,13 @@ _Use multiple accounts per opencode provider and switch automatically when one h
9
9
  [![opencode plugin](https://img.shields.io/badge/opencode-plugin-111?style=flat-square)](https://opencode.ai/docs/plugins)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)](LICENSE)
11
11
 
12
- [Features](#features) | [Installation](#installation) | [Usage](#usage) | [Commands](#commands) | [Troubleshooting](#troubleshooting)
12
+ [Features](#features) | [Installation](#installation) | [Usage](#usage) | [Fallback Commands](#fallback-commands) | [Troubleshooting](#troubleshooting)
13
13
 
14
14
  </div>
15
15
 
16
16
  `opencode-balancer` is an [opencode](https://opencode.ai/) plugin that lets you save multiple authenticated accounts for the same provider, give each one a friendly alias, and keep working when the active account becomes rate-limited.
17
17
 
18
- It works with opencode's existing auth flow: connect a provider, save the detected credentials as an alias, then let the plugin inject the active account credentials into future model requests.
18
+ It works with opencode's existing auth flow: connect a provider, save the detected credentials from the Balancer TUI, then let the plugin inject the active account credentials into future model requests.
19
19
 
20
20
  > [!NOTE]
21
21
  > This plugin manages credentials already configured through opencode. It does not create accounts, bypass provider limits, or modify provider-side quotas.
@@ -24,10 +24,10 @@ It works with opencode's existing auth flow: connect a provider, save the detect
24
24
 
25
25
  - **Multiple accounts per provider**: Save aliases like `work`, `personal`, or `backup` for the same provider.
26
26
  - **Automatic failover**: Switches to another saved account on retryable rate-limit/server responses.
27
- - **Native opencode commands**: Adds `/balancer` directly inside opencode.
28
- - **OAuth-friendly setup**: Adds an optional alias prompt to provider OAuth flows.
27
+ - **TUI-first account management**: Save aliases, switch accounts, and inspect pending connections from the Balancer sidebar/dashboard.
28
+ - **OAuth-friendly setup**: Uses opencode's native `/connect` flow, then prompts you to save the detected credentials in the TUI.
29
29
  - **Agent tool support**: Exposes a `balancer_command` tool so opencode agents can manage accounts when asked.
30
- - **Local credential store**: Saves account metadata under your opencode config directory.
30
+ - **Local credential store**: Saves account credentials and status data under your opencode config directory.
31
31
 
32
32
  ## Installation
33
33
 
@@ -62,60 +62,39 @@ Then restart opencode.
62
62
 
63
63
  ### Save Your First Account
64
64
 
65
- Connect a provider as usual:
65
+ Connect a provider with opencode's native flow:
66
66
 
67
67
  ```text
68
68
  /connect anthropic
69
69
  ```
70
70
 
71
- After the connection is detected, save it with an alias:
72
-
73
- ```text
74
- /balancer alias work
75
- ```
71
+ After the connection is detected, use the Balancer TUI modal or sidebar to save it with an alias such as `work`.
76
72
 
77
73
  ### Add Another Account
78
74
 
79
- Connect the same provider with a different account, then save it:
75
+ Connect the same provider with a different account, then save the new pending connection from the Balancer TUI:
80
76
 
81
77
  ```text
82
78
  /connect anthropic
83
- /balancer alias personal
84
- ```
85
-
86
- ### Switch Accounts Manually
87
-
88
- ```text
89
- /balancer use anthropic work
90
79
  ```
91
80
 
92
- ### List Saved Accounts
81
+ ### Manage Accounts
93
82
 
94
- ```text
95
- /balancer list
96
- ```
97
-
98
- Example output:
99
-
100
- ```text
101
- anthropic:
102
- * work (oauth, healthy)
103
- personal (oauth, healthy)
104
- ```
83
+ Use the Balancer sidebar or dashboard to switch accounts, view usage, and review pending connections. If the dashboard is not visible, open it from opencode's command palette.
105
84
 
106
85
  When the active account receives a retryable response such as `429`, `500`, `502`, `503`, `504`, or `529`, the plugin marks it as temporarily rate-limited and retries with another available account for the same provider.
107
86
 
108
- ## Commands
87
+ ## Fallback Commands
88
+
89
+ `opencode-balancer` is TUI-first. `/balancer` commands are compatibility and troubleshooting fallbacks, not the primary account management workflow. Alias creation is intentionally handled by the TUI pending-connection flow.
109
90
 
110
91
  | Command | Description |
111
92
  | --- | --- |
112
- | `/balancer help` | Show available commands. |
113
- | `/balancer alias <name>` | Save the last detected connection with an alias. |
114
- | `/balancer save <provider> <name>` | Save the provider's current native credentials. |
115
- | `/balancer use <provider> <name>` | Switch the active account for a provider. |
116
- | `/balancer list` | List saved accounts. |
117
- | `/balancer status` | Show saved accounts, last capture, and storage path. |
118
- | `/balancer remove <provider> <name>` | Remove a saved account. |
93
+ | `/balancer help` | Show fallback commands. |
94
+ | `/balancer list` | List saved accounts as `provider/alias`. |
95
+ | `/balancer status` | Show saved and pending counts. |
96
+ | `/balancer use <provider> <alias>` | Switch the active account for a provider. |
97
+ | `/balancer active <provider>` | Show the active account for a provider. |
119
98
 
120
99
  Aliases are normalized to lowercase and may contain letters, numbers, dots, hyphens, and underscores.
121
100
 
@@ -123,15 +102,15 @@ Aliases are normalized to lowercase and may contain letters, numbers, dots, hyph
123
102
 
124
103
  `opencode-balancer` hooks into opencode's plugin lifecycle and request flow:
125
104
 
126
- 1. It watches opencode auth changes and records the last detected provider credentials.
127
- 2. `/balancer alias <name>` stores those credentials under a provider-specific alias.
105
+ 1. It watches opencode auth changes and records detected provider credentials as pending connections.
106
+ 2. The Balancer TUI saves pending connections under provider-specific aliases.
128
107
  3. Before model requests, the plugin selects the active account for the request provider.
129
108
  4. If the provider returns a retryable rate-limit or server response, the plugin marks the account as temporarily unavailable and retries with another saved account.
130
109
 
131
110
  Saved account data is written to:
132
111
 
133
112
  ```text
134
- ~/.config/opencode/balancer-accounts.json
113
+ ~/.config/opencode/balancer.sqlite
135
114
  ```
136
115
 
137
116
  If `OPENCODE_CONFIG_DIR` is set, the plugin uses that directory instead.
@@ -161,9 +140,9 @@ To test a local checkout with opencode, point your config to the package directo
161
140
  | --- | --- |
162
141
  | Plugin does not load | Confirm `plugin` is singular, restart opencode, and check that the package name is `@thelioo/opencode-balancer@latest`. |
163
142
  | `/balancer` is unavailable | Restart opencode after editing the config. |
164
- | No connection detected | Run `/connect <provider>` first, then `/balancer alias <name>`. |
165
- | Account is not switching | Run `/balancer list` and confirm there is another healthy account for the same provider. |
166
- | Need to inspect storage | Run `/balancer status` to see the account store path. |
143
+ | No connection detected | Run `/connect <provider>` first, then save the pending connection from the Balancer TUI modal/sidebar. |
144
+ | Account is not switching | Open the Balancer sidebar/dashboard and confirm there is another saved account for the same provider. |
145
+ | Need command-line fallback | Run `/balancer help` for compatibility commands. |
167
146
 
168
147
  ## Resources
169
148
 
@@ -0,0 +1,14 @@
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;
@@ -0,0 +1,260 @@
1
+ import { now } from "./time";
2
+ const authTypes = ["api", "oauth", "wellknown"];
3
+ export function normalizeAlias(alias) {
4
+ return alias
5
+ .trim()
6
+ .toLowerCase()
7
+ .replace(/[^a-z0-9._-]+/g, "-")
8
+ .replace(/^-+|-+$/g, "");
9
+ }
10
+ function accountRowID(row) {
11
+ return `${row.provider_id}/${row.alias}`;
12
+ }
13
+ function isAuthType(value) {
14
+ return authTypes.includes(value);
15
+ }
16
+ function parseAccountAuth(row) {
17
+ const id = accountRowID(row);
18
+ let auth;
19
+ try {
20
+ auth = JSON.parse(row.auth_json);
21
+ }
22
+ catch {
23
+ throw new Error(`Invalid account row for ${id}: auth_json is invalid`);
24
+ }
25
+ if (!auth || typeof auth !== "object" || !("type" in auth) || typeof auth.type !== "string") {
26
+ throw new Error(`Invalid account row for ${id}: auth_json type is invalid`);
27
+ }
28
+ if (!isAuthType(auth.type)) {
29
+ throw new Error(`Invalid account row for ${id}: auth_json type ${auth.type} is invalid`);
30
+ }
31
+ return auth;
32
+ }
33
+ function validateAccountAuthType(row) {
34
+ if (!isAuthType(row.auth_type)) {
35
+ throw new Error(`Invalid account row for ${accountRowID(row)}: auth_type ${row.auth_type} is invalid`);
36
+ }
37
+ return row.auth_type;
38
+ }
39
+ function validateDisabled(row) {
40
+ if (row.disabled !== 0 && row.disabled !== 1) {
41
+ throw new Error(`Invalid account row for ${accountRowID(row)}: disabled ${row.disabled} is invalid`);
42
+ }
43
+ return Boolean(row.disabled);
44
+ }
45
+ function accountFromRow(row) {
46
+ const auth = parseAccountAuth(row);
47
+ const authType = validateAccountAuthType(row);
48
+ if (auth.type !== authType) {
49
+ throw new Error(`Invalid account row for ${accountRowID(row)}: auth_type ${authType} does not match auth_json type ${auth.type}`);
50
+ }
51
+ return {
52
+ providerID: row.provider_id,
53
+ alias: row.alias,
54
+ auth,
55
+ authType,
56
+ createdAt: row.created_at,
57
+ updatedAt: row.updated_at,
58
+ lastUsedAt: row.last_used_at ?? undefined,
59
+ rateLimitedUntil: row.rate_limited_until ?? undefined,
60
+ failures: row.failures,
61
+ disabled: validateDisabled(row),
62
+ };
63
+ }
64
+ function setActiveAlias(db, providerID, alias) {
65
+ db.query(`INSERT INTO provider_state (provider_id, active_alias, updated_at, metadata_json)
66
+ VALUES (?, ?, ?, '{}')
67
+ ON CONFLICT(provider_id) DO UPDATE SET
68
+ active_alias = excluded.active_alias,
69
+ updated_at = excluded.updated_at`).run(providerID, alias, now());
70
+ db.query(`INSERT INTO settings (key, value) VALUES ('selected_provider_id', ?)
71
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(providerID);
72
+ }
73
+ function parseMetadataJson(value) {
74
+ if (!value)
75
+ return {};
76
+ try {
77
+ const metadata = JSON.parse(value);
78
+ return metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : {};
79
+ }
80
+ catch {
81
+ return {};
82
+ }
83
+ }
84
+ export function saveAccount(db, providerID, aliasInput, auth) {
85
+ const alias = normalizeAlias(aliasInput);
86
+ if (!alias)
87
+ throw new Error("Invalid alias");
88
+ const save = db.transaction(() => {
89
+ const timestamp = now();
90
+ db.query(`INSERT INTO accounts (
91
+ provider_id,
92
+ alias,
93
+ auth_json,
94
+ auth_type,
95
+ created_at,
96
+ updated_at,
97
+ failures,
98
+ disabled
99
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
100
+ ON CONFLICT(provider_id, alias) DO UPDATE SET
101
+ auth_json = excluded.auth_json,
102
+ auth_type = excluded.auth_type,
103
+ updated_at = excluded.updated_at`).run(providerID, alias, JSON.stringify(auth), auth.type, timestamp, timestamp, 0, 0);
104
+ setActiveAlias(db, providerID, alias);
105
+ const account = getAccount(db, providerID, alias);
106
+ if (!account)
107
+ throw new Error(`Account not found: ${providerID}/${alias}`);
108
+ return account;
109
+ });
110
+ return save();
111
+ }
112
+ export function updateAccountAuth(db, providerID, aliasInput, auth) {
113
+ const alias = normalizeAlias(aliasInput);
114
+ if (!alias)
115
+ throw new Error("Invalid alias");
116
+ db.query(`UPDATE accounts
117
+ SET auth_json = ?,
118
+ auth_type = ?,
119
+ updated_at = ?
120
+ WHERE provider_id = ? AND alias = ?`).run(JSON.stringify(auth), auth.type, now(), providerID, alias);
121
+ return getAccount(db, providerID, alias);
122
+ }
123
+ export function renameAccount(db, providerID, fromAliasInput, toAliasInput) {
124
+ const fromAlias = normalizeAlias(fromAliasInput);
125
+ const toAlias = normalizeAlias(toAliasInput);
126
+ if (!fromAlias || !toAlias)
127
+ throw new Error("Invalid alias");
128
+ if (fromAlias === toAlias) {
129
+ const account = getAccount(db, providerID, fromAlias);
130
+ if (!account)
131
+ throw new Error(`Account not found: ${providerID}/${fromAlias}`);
132
+ return account;
133
+ }
134
+ const rename = db.transaction(() => {
135
+ const existing = getAccount(db, providerID, fromAlias);
136
+ if (!existing)
137
+ throw new Error(`Account not found: ${providerID}/${fromAlias}`);
138
+ if (getAccount(db, providerID, toAlias))
139
+ throw new Error(`Account already exists: ${providerID}/${toAlias}`);
140
+ db.query(`UPDATE accounts
141
+ SET alias = ?, updated_at = ?
142
+ WHERE provider_id = ? AND alias = ?`).run(toAlias, now(), providerID, fromAlias);
143
+ db.query(`UPDATE usage_snapshots
144
+ SET alias = ?
145
+ WHERE provider_id = ? AND alias = ?`).run(toAlias, providerID, fromAlias);
146
+ const active = db
147
+ .query("SELECT active_alias FROM provider_state WHERE provider_id = ?")
148
+ .get(providerID);
149
+ if (active?.active_alias === fromAlias)
150
+ setActiveAlias(db, providerID, toAlias);
151
+ const account = getAccount(db, providerID, toAlias);
152
+ if (!account)
153
+ throw new Error(`Account not found: ${providerID}/${toAlias}`);
154
+ return account;
155
+ });
156
+ return rename();
157
+ }
158
+ export function getAccount(db, providerID, alias) {
159
+ const normalizedAlias = normalizeAlias(alias);
160
+ const row = db
161
+ .query(`SELECT provider_id, alias, auth_json, auth_type, created_at, updated_at,
162
+ last_used_at, rate_limited_until, failures, disabled
163
+ FROM accounts
164
+ WHERE provider_id = ? AND alias = ?`)
165
+ .get(providerID, normalizedAlias);
166
+ return row ? accountFromRow(row) : undefined;
167
+ }
168
+ export function listAccounts(db, providerID) {
169
+ const sql = `SELECT provider_id, alias, auth_json, auth_type, created_at, updated_at,
170
+ last_used_at, rate_limited_until, failures, disabled
171
+ FROM accounts`;
172
+ const rows = providerID
173
+ ? db
174
+ .query(`${sql} WHERE provider_id = ? ORDER BY alias`)
175
+ .all(providerID)
176
+ : db.query(`${sql} ORDER BY provider_id, alias`).all();
177
+ return rows.map(accountFromRow);
178
+ }
179
+ export function setActiveAccount(db, providerID, alias) {
180
+ const normalizedAlias = normalizeAlias(alias);
181
+ const account = getAccount(db, providerID, normalizedAlias);
182
+ if (!account)
183
+ throw new Error(`Account not found: ${providerID}/${normalizedAlias}`);
184
+ setActiveAlias(db, providerID, normalizedAlias);
185
+ return account;
186
+ }
187
+ export function removeAccount(db, providerID, alias) {
188
+ const normalizedAlias = normalizeAlias(alias);
189
+ const remove = db.transaction(() => {
190
+ const existing = getAccount(db, providerID, normalizedAlias);
191
+ if (!existing)
192
+ return false;
193
+ db.query("DELETE FROM accounts WHERE provider_id = ? AND alias = ?").run(providerID, normalizedAlias);
194
+ db.query("DELETE FROM usage_snapshots WHERE provider_id = ? AND alias = ?").run(providerID, normalizedAlias);
195
+ const active = db
196
+ .query("SELECT active_alias FROM provider_state WHERE provider_id = ?")
197
+ .get(providerID);
198
+ if (active?.active_alias === normalizedAlias) {
199
+ const next = db
200
+ .query("SELECT alias FROM accounts WHERE provider_id = ? ORDER BY alias LIMIT 1")
201
+ .get(providerID);
202
+ db.query(`UPDATE provider_state
203
+ SET active_alias = ?, updated_at = ?
204
+ WHERE provider_id = ?`).run(next?.alias ?? null, now(), providerID);
205
+ }
206
+ return true;
207
+ });
208
+ return remove();
209
+ }
210
+ export function getActiveAccount(db, providerID) {
211
+ const row = db
212
+ .query("SELECT active_alias FROM provider_state WHERE provider_id = ?")
213
+ .get(providerID);
214
+ if (!row?.active_alias)
215
+ return undefined;
216
+ return getAccount(db, providerID, row.active_alias);
217
+ }
218
+ export function getSelectedAccount(db) {
219
+ const selectedProvider = db
220
+ .query("SELECT value FROM settings WHERE key = 'selected_provider_id'")
221
+ .get();
222
+ if (selectedProvider?.value) {
223
+ const active = getActiveAccount(db, selectedProvider.value);
224
+ if (active)
225
+ return active;
226
+ }
227
+ const row = db
228
+ .query(`SELECT provider_id, active_alias
229
+ FROM provider_state
230
+ WHERE active_alias IS NOT NULL
231
+ ORDER BY updated_at DESC, provider_id
232
+ LIMIT 1`)
233
+ .get();
234
+ if (!row?.active_alias)
235
+ return undefined;
236
+ return getAccount(db, row.provider_id, row.active_alias);
237
+ }
238
+ export function setSelectedModel(db, providerID, modelID) {
239
+ const row = db
240
+ .query("SELECT metadata_json FROM provider_state WHERE provider_id = ?")
241
+ .get(providerID);
242
+ const metadata = parseMetadataJson(row?.metadata_json);
243
+ metadata.selected_model_id = modelID;
244
+ db.query(`INSERT INTO provider_state (provider_id, active_alias, updated_at, metadata_json)
245
+ VALUES (?, NULL, ?, ?)
246
+ ON CONFLICT(provider_id) DO UPDATE SET
247
+ updated_at = excluded.updated_at,
248
+ metadata_json = excluded.metadata_json`).run(providerID, now(), JSON.stringify(metadata));
249
+ return { providerID, modelID };
250
+ }
251
+ export function getSelectedModel(db, providerID) {
252
+ const row = db
253
+ .query("SELECT metadata_json FROM provider_state WHERE provider_id = ?")
254
+ .get(providerID);
255
+ const modelID = parseMetadataJson(row?.metadata_json).selected_model_id;
256
+ if (typeof modelID !== "string" || modelID.length === 0)
257
+ return undefined;
258
+ return { providerID, modelID };
259
+ }
260
+ //# sourceMappingURL=accounts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounts.js","sourceRoot":"","sources":["../../src/core/accounts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { Database } from "bun:sqlite";
2
+ export declare function secureDatabaseFiles(path: string): void;
3
+ export declare function openBalancerDatabase(path: string): Database;
4
+ export declare function closeBalancerDatabase(path: string): void;
@@ -0,0 +1,69 @@
1
+ import { chmodSync, mkdirSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { Database } from "bun:sqlite";
4
+ const databases = new Map();
5
+ function isMissingFileError(error) {
6
+ return error.code === "ENOENT";
7
+ }
8
+ function isClosedDatabaseError(error) {
9
+ return error instanceof Error && /closed/i.test(error.message);
10
+ }
11
+ function cachedDatabaseIsOpen(database) {
12
+ try {
13
+ database.exec("SELECT 1;");
14
+ return true;
15
+ }
16
+ catch (error) {
17
+ if (isClosedDatabaseError(error))
18
+ return false;
19
+ throw error;
20
+ }
21
+ }
22
+ export function secureDatabaseFiles(path) {
23
+ for (const file of [path, `${path}-wal`, `${path}-shm`]) {
24
+ try {
25
+ chmodSync(file, 0o600);
26
+ }
27
+ catch (error) {
28
+ if (!isMissingFileError(error))
29
+ throw error;
30
+ }
31
+ }
32
+ }
33
+ export function openBalancerDatabase(path) {
34
+ const existing = databases.get(path);
35
+ if (existing) {
36
+ if (cachedDatabaseIsOpen(existing))
37
+ return existing;
38
+ databases.delete(path);
39
+ }
40
+ mkdirSync(dirname(path), { recursive: true });
41
+ const database = new Database(path);
42
+ try {
43
+ database.exec("PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON;");
44
+ secureDatabaseFiles(path);
45
+ databases.set(path, database);
46
+ return database;
47
+ }
48
+ catch (error) {
49
+ try {
50
+ database.close();
51
+ }
52
+ catch { }
53
+ throw error;
54
+ }
55
+ }
56
+ export function closeBalancerDatabase(path) {
57
+ const existing = databases.get(path);
58
+ if (!existing)
59
+ return;
60
+ databases.delete(path);
61
+ try {
62
+ existing.close();
63
+ }
64
+ catch (error) {
65
+ if (!isClosedDatabaseError(error))
66
+ throw error;
67
+ }
68
+ }
69
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/core/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;AAE9C,SAAS,kBAAkB,CAAC,KAAc;IACtC,OAAQ,KAA2B,CAAC,IAAI,KAAK,QAAQ,CAAC;AAC1D,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAc;IACzC,OAAO,KAAK,YAAY,KAAK,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,oBAAoB,CAAC,QAAkB;IAC5C,IAAI,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,qBAAqB,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAC/C,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC5C,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,EAAE,CAAC;QACtD,IAAI,CAAC;YACD,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC;gBAAE,MAAM,KAAK,CAAC;QAChD,CAAC;IACL,CAAC;AACL,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC7C,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,QAAQ,EAAE,CAAC;QACX,IAAI,oBAAoB,CAAC,QAAQ,CAAC;YAAE,OAAO,QAAQ,CAAC;QACpD,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;QACtE,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC1B,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC9B,OAAO,QAAQ,CAAC;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,CAAC;YACD,QAAQ,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACV,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAC9C,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,QAAQ;QAAE,OAAO;IAEtB,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC;QACD,QAAQ,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC;YAAE,MAAM,KAAK,CAAC;IACnD,CAAC;AACL,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import type { BalancerEvent, BalancerEventType } from "./types";
3
+ export declare function appendEvent(db: Database, input: {
4
+ type: BalancerEventType;
5
+ providerID?: string;
6
+ alias?: string;
7
+ message: string;
8
+ metadata?: Record<string, string>;
9
+ }): BalancerEvent;
10
+ export declare function listEvents(db: Database, limit?: number): {
11
+ id: string;
12
+ type: BalancerEventType;
13
+ providerID: string | undefined;
14
+ alias: string | undefined;
15
+ message: string;
16
+ createdAt: number;
17
+ metadata: Record<string, string>;
18
+ }[];
@@ -0,0 +1,39 @@
1
+ import { now } from "./time";
2
+ export function appendEvent(db, input) {
3
+ const event = {
4
+ id: crypto.randomUUID(),
5
+ type: input.type,
6
+ providerID: input.providerID,
7
+ alias: input.alias,
8
+ message: input.message,
9
+ createdAt: now(),
10
+ metadata: input.metadata ?? {},
11
+ };
12
+ db.query(`INSERT INTO events (id, type, provider_id, alias, message, created_at, metadata_json)
13
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(event.id, event.type, event.providerID ?? null, event.alias ?? null, event.message, event.createdAt, JSON.stringify(event.metadata));
14
+ return event;
15
+ }
16
+ export function listEvents(db, limit = 50) {
17
+ return db
18
+ .query("SELECT * FROM events ORDER BY created_at DESC LIMIT ?")
19
+ .all(limit)
20
+ .map((row) => {
21
+ let metadata;
22
+ try {
23
+ metadata = JSON.parse(row.metadata_json);
24
+ }
25
+ catch (error) {
26
+ throw new Error(`Invalid event metadata JSON for event ${row.id}`, { cause: error });
27
+ }
28
+ return {
29
+ id: row.id,
30
+ type: row.type,
31
+ providerID: row.provider_id ?? undefined,
32
+ alias: row.alias ?? undefined,
33
+ message: row.message,
34
+ createdAt: row.created_at,
35
+ metadata,
36
+ };
37
+ });
38
+ }
39
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/core/events.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAa7B,MAAM,UAAU,WAAW,CACvB,EAAY,EACZ,KAMC;IAED,MAAM,KAAK,GAAkB;QACzB,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;QACvB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,SAAS,EAAE,GAAG,EAAE;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,EAAE;KACjC,CAAC;IACF,EAAE,CAAC,KAAK,CACJ;sCAC8B,CACjC,CAAC,GAAG,CACD,KAAK,CAAC,EAAE,EACR,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,UAAU,IAAI,IAAI,EACxB,KAAK,CAAC,KAAK,IAAI,IAAI,EACnB,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,SAAS,EACf,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CACjC,CAAC;IACF,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EAAY,EAAE,KAAK,GAAG,EAAE;IAC/C,OAAO,EAAE;SACJ,KAAK,CAAqB,uDAAuD,CAAC;SAClF,GAAG,CAAC,KAAK,CAAC;SACV,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACT,IAAI,QAAgC,CAAC;QACrC,IAAI,CAAC;YACD,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAA2B,CAAC;QACvE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,yCAAyC,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QACzF,CAAC;QAED,OAAO;YACH,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,UAAU,EAAE,GAAG,CAAC,WAAW,IAAI,SAAS;YACxC,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,SAAS;YAC7B,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,SAAS,EAAE,GAAG,CAAC,UAAU;YACzB,QAAQ;SACX,CAAC;IACN,CAAC,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Database } from "bun:sqlite";
2
+ export declare function suppressNativeAuthCapture(db: Database, providerID: string, durationMs?: number): void;
3
+ export declare function isNativeAuthCaptureSuppressed(db: Database, providerID: string): boolean;
@@ -0,0 +1,19 @@
1
+ import { now } from "./time";
2
+ function key(providerID) {
3
+ return `native_auth_suppressed_until:${providerID}`;
4
+ }
5
+ export function suppressNativeAuthCapture(db, providerID, durationMs = 10_000) {
6
+ db.query(`INSERT INTO settings (key, value) VALUES (?, ?)
7
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`).run(key(providerID), String(now() + durationMs));
8
+ }
9
+ export function isNativeAuthCaptureSuppressed(db, providerID) {
10
+ const settingKey = key(providerID);
11
+ const row = db.query("SELECT value FROM settings WHERE key = ?").get(settingKey);
12
+ const suppressedUntil = Number(row?.value ?? 0);
13
+ if (Number.isFinite(suppressedUntil) && suppressedUntil > now())
14
+ return true;
15
+ if (row)
16
+ db.query("DELETE FROM settings WHERE key = ?").run(settingKey);
17
+ return false;
18
+ }
19
+ //# sourceMappingURL=native-auth-suppression.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-auth-suppression.js","sourceRoot":"","sources":["../../src/core/native-auth-suppression.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAE7B,SAAS,GAAG,CAAC,UAAkB;IAC3B,OAAO,gCAAgC,UAAU,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,EAAY,EAAE,UAAkB,EAAE,UAAU,GAAG,MAAM;IAC3F,EAAE,CAAC,KAAK,CACJ;+DACuD,CAC1D,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,EAAY,EAAE,UAAkB;IAC1E,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,EAAE,CAAC,KAAK,CAA8B,0CAA0C,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC9G,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC;IAChD,IAAI,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,eAAe,GAAG,GAAG,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7E,IAAI,GAAG;QAAE,EAAE,CAAC,KAAK,CAAoB,oCAAoC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC3F,OAAO,KAAK,CAAC;AACjB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Database } from "bun:sqlite";
2
+ export declare function markNativeConnectInProgress(db: Database, durationMs?: number): void;
3
+ export declare function isNativeConnectInProgress(db: Database): boolean;
4
+ export declare function clearNativeConnectInProgress(db: Database): void;