@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.
- package/INSTALL.txt +53 -25
- package/README.md +95 -51
- package/dist/core/accounts.ts +404 -0
- package/dist/core/database.ts +67 -0
- package/dist/core/events.ts +75 -0
- package/dist/core/native-auth-suppression.ts +36 -0
- package/dist/core/native-connect.ts +31 -0
- package/dist/core/path.ts +34 -0
- package/dist/core/pending.ts +351 -0
- package/dist/core/priority.ts +193 -0
- package/dist/core/schema.ts +439 -0
- package/dist/core/time.ts +3 -0
- package/dist/core/types.ts +72 -0
- package/dist/core/usage/index.ts +23 -0
- package/dist/core/usage/providers/copilot.ts +243 -0
- package/dist/core/usage/providers/openai.ts +179 -0
- package/dist/core/usage/redact.ts +80 -0
- package/dist/core/usage/store.ts +66 -0
- package/dist/core/usage/types.ts +24 -0
- package/dist/index.js +173 -4
- package/dist/index.js.map +1 -1
- package/dist/server/auth-watcher.ts +318 -0
- package/dist/server/commands.ts +58 -0
- package/dist/server/fetch-patch.ts +162 -0
- package/dist/server/index.ts +134 -0
- package/dist/server/native.ts +49 -0
- package/dist/server/request-balancer.ts +67 -0
- package/dist/tui/actions.ts +176 -112
- package/dist/tui/balancer-bar-sync.ts +55 -45
- package/dist/tui/components/alias-dialog.tsx +71 -56
- package/dist/tui/components/dashboard.tsx +530 -358
- package/dist/tui/components/priority-screen.tsx +389 -267
- package/dist/tui/components/provider-model-dialog.tsx +71 -64
- package/dist/tui/components/rename-dialog.tsx +35 -28
- package/dist/tui/components/sidebar.tsx +103 -79
- package/dist/tui/components/status-indicator.tsx +78 -59
- package/dist/tui/components/usage-bar.tsx +18 -7
- package/dist/tui/components/usage-display.tsx +32 -16
- package/dist/tui/connect.ts +104 -73
- package/dist/tui/dashboard-keys.ts +53 -41
- package/dist/tui/native-model-apply.ts +45 -36
- package/dist/tui/priority-keys.ts +44 -36
- package/dist/tui/provider-models.ts +32 -25
- package/dist/tui/responsive.ts +10 -7
- package/dist/tui/selected-account-bar-sync.ts +23 -23
- package/dist/tui/selection-colors.ts +38 -30
- package/dist/tui/state.ts +61 -44
- package/dist/tui/status-format.ts +24 -20
- package/dist/tui/tui.js +165 -153
- package/dist/tui/tui.js.map +1 -1
- package/dist/tui/tui.tsx +194 -144
- package/dist/tui/usage-auto-refresh.ts +52 -45
- package/dist/tui/usage-format.ts +9 -9
- package/package.json +61 -52
- package/dist/core/accounts.d.ts +0 -14
- package/dist/core/accounts.js +0 -260
- package/dist/core/accounts.js.map +0 -1
- package/dist/core/database.d.ts +0 -4
- package/dist/core/database.js +0 -69
- package/dist/core/database.js.map +0 -1
- package/dist/core/events.d.ts +0 -18
- package/dist/core/events.js +0 -39
- package/dist/core/events.js.map +0 -1
- package/dist/core/native-auth-suppression.d.ts +0 -3
- package/dist/core/native-auth-suppression.js +0 -19
- package/dist/core/native-auth-suppression.js.map +0 -1
- package/dist/core/native-connect.d.ts +0 -4
- package/dist/core/native-connect.js +0 -19
- package/dist/core/native-connect.js.map +0 -1
- package/dist/core/path.d.ts +0 -4
- package/dist/core/path.js +0 -26
- package/dist/core/path.js.map +0 -1
- package/dist/core/pending.d.ts +0 -9
- package/dist/core/pending.js +0 -237
- package/dist/core/pending.js.map +0 -1
- package/dist/core/priority.d.ts +0 -20
- package/dist/core/priority.js +0 -120
- package/dist/core/priority.js.map +0 -1
- package/dist/core/schema.d.ts +0 -2
- package/dist/core/schema.js +0 -265
- package/dist/core/schema.js.map +0 -1
- package/dist/core/time.d.ts +0 -1
- package/dist/core/time.js +0 -4
- package/dist/core/time.js.map +0 -1
- package/dist/core/types.d.ts +0 -59
- package/dist/core/types.js +0 -2
- package/dist/core/types.js.map +0 -1
- package/dist/core/usage/index.d.ts +0 -4
- package/dist/core/usage/index.js +0 -16
- package/dist/core/usage/index.js.map +0 -1
- package/dist/core/usage/providers/copilot.d.ts +0 -2
- package/dist/core/usage/providers/copilot.js +0 -169
- package/dist/core/usage/providers/copilot.js.map +0 -1
- package/dist/core/usage/providers/openai.d.ts +0 -2
- package/dist/core/usage/providers/openai.js +0 -133
- package/dist/core/usage/providers/openai.js.map +0 -1
- package/dist/core/usage/redact.d.ts +0 -3
- package/dist/core/usage/redact.js +0 -67
- package/dist/core/usage/redact.js.map +0 -1
- package/dist/core/usage/store.d.ts +0 -4
- package/dist/core/usage/store.js +0 -31
- package/dist/core/usage/store.js.map +0 -1
- package/dist/core/usage/types.d.ts +0 -21
- package/dist/core/usage/types.js +0 -2
- package/dist/core/usage/types.js.map +0 -1
- package/dist/index.d.ts +0 -5
- package/dist/server/auth-watcher.d.ts +0 -32
- package/dist/server/auth-watcher.js +0 -227
- package/dist/server/auth-watcher.js.map +0 -1
- package/dist/server/commands.d.ts +0 -2
- package/dist/server/commands.js +0 -46
- package/dist/server/commands.js.map +0 -1
- package/dist/server/fetch-patch.d.ts +0 -3
- package/dist/server/fetch-patch.js +0 -118
- package/dist/server/fetch-patch.js.map +0 -1
- package/dist/server/index.d.ts +0 -8
- package/dist/server/index.js +0 -94
- package/dist/server/index.js.map +0 -1
- package/dist/server/native.d.ts +0 -6
- package/dist/server/native.js +0 -35
- package/dist/server/native.js.map +0 -1
- package/dist/server/request-balancer.d.ts +0 -16
- package/dist/server/request-balancer.js +0 -43
- package/dist/server/request-balancer.js.map +0 -1
- package/dist/tui/actions.d.ts +0 -41
- package/dist/tui/actions.js +0 -92
- package/dist/tui/actions.js.map +0 -1
- package/dist/tui/balancer-bar-sync.d.ts +0 -19
- package/dist/tui/balancer-bar-sync.js +0 -45
- package/dist/tui/balancer-bar-sync.js.map +0 -1
- package/dist/tui/components/alias-dialog.d.ts +0 -4
- package/dist/tui/components/dashboard.d.ts +0 -12
- package/dist/tui/components/priority-screen.d.ts +0 -9
- package/dist/tui/components/provider-model-dialog.d.ts +0 -14
- package/dist/tui/components/rename-dialog.d.ts +0 -4
- package/dist/tui/components/sidebar.d.ts +0 -10
- package/dist/tui/components/status-indicator.d.ts +0 -9
- package/dist/tui/components/usage-bar.d.ts +0 -8
- package/dist/tui/components/usage-display.d.ts +0 -10
- package/dist/tui/connect.d.ts +0 -30
- package/dist/tui/connect.js +0 -75
- package/dist/tui/connect.js.map +0 -1
- package/dist/tui/dashboard-keys.d.ts +0 -45
- package/dist/tui/dashboard-keys.js +0 -44
- package/dist/tui/dashboard-keys.js.map +0 -1
- package/dist/tui/native-model-apply.d.ts +0 -21
- package/dist/tui/native-model-apply.js +0 -53
- package/dist/tui/native-model-apply.js.map +0 -1
- package/dist/tui/priority-keys.d.ts +0 -40
- package/dist/tui/priority-keys.js +0 -38
- package/dist/tui/priority-keys.js.map +0 -1
- package/dist/tui/provider-models.d.ts +0 -19
- package/dist/tui/provider-models.js +0 -17
- package/dist/tui/provider-models.js.map +0 -1
- package/dist/tui/responsive.d.ts +0 -9
- package/dist/tui/responsive.js +0 -13
- package/dist/tui/responsive.js.map +0 -1
- package/dist/tui/selected-account-bar-sync.d.ts +0 -10
- package/dist/tui/selected-account-bar-sync.js +0 -26
- package/dist/tui/selected-account-bar-sync.js.map +0 -1
- package/dist/tui/selection-colors.d.ts +0 -10
- package/dist/tui/selection-colors.js +0 -38
- package/dist/tui/selection-colors.js.map +0 -1
- package/dist/tui/state.d.ts +0 -14
- package/dist/tui/state.js +0 -46
- package/dist/tui/state.js.map +0 -1
- package/dist/tui/status-format.d.ts +0 -15
- package/dist/tui/status-format.js +0 -17
- package/dist/tui/status-format.js.map +0 -1
- package/dist/tui/tui.d.ts +0 -7
- package/dist/tui/usage-auto-refresh.d.ts +0 -16
- package/dist/tui/usage-auto-refresh.js +0 -46
- package/dist/tui/usage-auto-refresh.js.map +0 -1
- package/dist/tui/usage-format.d.ts +0 -2
- package/dist/tui/usage-format.js +0 -17
- package/dist/tui/usage-format.js.map +0 -1
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
const authTypes = new Set(["api", "oauth", "wellknown"]);
|
|
4
|
+
|
|
5
|
+
function hasMigration(db: Database, version: number) {
|
|
6
|
+
const row = db
|
|
7
|
+
.query<{ version: number }, [number]>(
|
|
8
|
+
"SELECT version FROM schema_migrations WHERE version = ?",
|
|
9
|
+
)
|
|
10
|
+
.get(version);
|
|
11
|
+
return Boolean(row);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function hasTable(db: Database, table: string) {
|
|
15
|
+
const row = db
|
|
16
|
+
.query<{ name: string }, [string]>(
|
|
17
|
+
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
18
|
+
)
|
|
19
|
+
.get(table);
|
|
20
|
+
return Boolean(row);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function tableColumns(db: Database, table: string) {
|
|
24
|
+
if (!hasTable(db, table)) return new Set<string>();
|
|
25
|
+
return new Set(
|
|
26
|
+
db
|
|
27
|
+
.query<{ name: string }, []>(`PRAGMA table_info(${table})`)
|
|
28
|
+
.all()
|
|
29
|
+
.map((row) => row.name),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function inferAuthType(authJSON: string) {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(authJSON) as { type?: unknown };
|
|
36
|
+
if (typeof parsed.type === "string" && authTypes.has(parsed.type))
|
|
37
|
+
return parsed.type;
|
|
38
|
+
} catch {
|
|
39
|
+
// Invalid legacy rows will be rejected by row validation when read.
|
|
40
|
+
}
|
|
41
|
+
return "api";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function selectExisting(db: Database, table: string, columns: string[]) {
|
|
45
|
+
const existing = tableColumns(db, table);
|
|
46
|
+
const selected = columns.filter((column) => existing.has(column));
|
|
47
|
+
if (selected.length === 0) return [];
|
|
48
|
+
return db
|
|
49
|
+
.query<Record<string, unknown>, []>(
|
|
50
|
+
`SELECT ${selected.join(", ")} FROM ${table}`,
|
|
51
|
+
)
|
|
52
|
+
.all();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function recreateAccounts(db: Database) {
|
|
56
|
+
const rows = selectExisting(db, "accounts", [
|
|
57
|
+
"provider_id",
|
|
58
|
+
"alias",
|
|
59
|
+
"auth_json",
|
|
60
|
+
"auth_type",
|
|
61
|
+
"created_at",
|
|
62
|
+
"updated_at",
|
|
63
|
+
"last_used_at",
|
|
64
|
+
"rate_limited_until",
|
|
65
|
+
"failures",
|
|
66
|
+
"disabled",
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
db.exec(`
|
|
70
|
+
DROP TABLE IF EXISTS accounts_new;
|
|
71
|
+
CREATE TABLE accounts_new (
|
|
72
|
+
provider_id TEXT NOT NULL,
|
|
73
|
+
alias TEXT NOT NULL,
|
|
74
|
+
auth_json TEXT NOT NULL,
|
|
75
|
+
auth_type TEXT NOT NULL,
|
|
76
|
+
created_at INTEGER NOT NULL,
|
|
77
|
+
updated_at INTEGER NOT NULL,
|
|
78
|
+
last_used_at INTEGER,
|
|
79
|
+
rate_limited_until INTEGER,
|
|
80
|
+
failures INTEGER NOT NULL DEFAULT 0,
|
|
81
|
+
disabled INTEGER NOT NULL DEFAULT 0,
|
|
82
|
+
PRIMARY KEY (provider_id, alias)
|
|
83
|
+
);
|
|
84
|
+
`);
|
|
85
|
+
|
|
86
|
+
const insert = db.query<
|
|
87
|
+
unknown,
|
|
88
|
+
[
|
|
89
|
+
string,
|
|
90
|
+
string,
|
|
91
|
+
string,
|
|
92
|
+
string,
|
|
93
|
+
number,
|
|
94
|
+
number,
|
|
95
|
+
number | null,
|
|
96
|
+
number | null,
|
|
97
|
+
number,
|
|
98
|
+
number,
|
|
99
|
+
]
|
|
100
|
+
>(
|
|
101
|
+
`INSERT OR REPLACE INTO accounts_new (
|
|
102
|
+
provider_id, alias, auth_json, auth_type, created_at, updated_at,
|
|
103
|
+
last_used_at, rate_limited_until, failures, disabled
|
|
104
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
105
|
+
);
|
|
106
|
+
for (const row of rows) {
|
|
107
|
+
if (
|
|
108
|
+
typeof row.provider_id !== "string" ||
|
|
109
|
+
typeof row.alias !== "string" ||
|
|
110
|
+
typeof row.auth_json !== "string"
|
|
111
|
+
)
|
|
112
|
+
continue;
|
|
113
|
+
const authType =
|
|
114
|
+
typeof row.auth_type === "string" && authTypes.has(row.auth_type)
|
|
115
|
+
? row.auth_type
|
|
116
|
+
: inferAuthType(row.auth_json);
|
|
117
|
+
insert.run(
|
|
118
|
+
row.provider_id,
|
|
119
|
+
row.alias,
|
|
120
|
+
row.auth_json,
|
|
121
|
+
authType,
|
|
122
|
+
typeof row.created_at === "number" ? row.created_at : Date.now(),
|
|
123
|
+
typeof row.updated_at === "number" ? row.updated_at : Date.now(),
|
|
124
|
+
typeof row.last_used_at === "number" ? row.last_used_at : null,
|
|
125
|
+
typeof row.rate_limited_until === "number"
|
|
126
|
+
? row.rate_limited_until
|
|
127
|
+
: null,
|
|
128
|
+
typeof row.failures === "number" ? row.failures : 0,
|
|
129
|
+
row.disabled === 1 ? 1 : 0,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
db.exec(`
|
|
134
|
+
DROP TABLE IF EXISTS accounts;
|
|
135
|
+
ALTER TABLE accounts_new RENAME TO accounts;
|
|
136
|
+
`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function recreateProviderState(db: Database) {
|
|
140
|
+
const rows = selectExisting(db, "provider_state", [
|
|
141
|
+
"provider_id",
|
|
142
|
+
"active_alias",
|
|
143
|
+
"updated_at",
|
|
144
|
+
"metadata_json",
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
db.exec(`
|
|
148
|
+
DROP TABLE IF EXISTS provider_state_new;
|
|
149
|
+
CREATE TABLE provider_state_new (
|
|
150
|
+
provider_id TEXT PRIMARY KEY,
|
|
151
|
+
active_alias TEXT,
|
|
152
|
+
updated_at INTEGER NOT NULL,
|
|
153
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
154
|
+
);
|
|
155
|
+
`);
|
|
156
|
+
|
|
157
|
+
const insert = db.query<unknown, [string, string | null, number, string]>(
|
|
158
|
+
`INSERT OR REPLACE INTO provider_state_new (provider_id, active_alias, updated_at, metadata_json)
|
|
159
|
+
VALUES (?, ?, ?, ?)`,
|
|
160
|
+
);
|
|
161
|
+
for (const row of rows) {
|
|
162
|
+
if (typeof row.provider_id !== "string") continue;
|
|
163
|
+
insert.run(
|
|
164
|
+
row.provider_id,
|
|
165
|
+
typeof row.active_alias === "string" ? row.active_alias : null,
|
|
166
|
+
typeof row.updated_at === "number" ? row.updated_at : Date.now(),
|
|
167
|
+
typeof row.metadata_json === "string" ? row.metadata_json : "{}",
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
db.exec(`
|
|
172
|
+
DROP TABLE IF EXISTS provider_state;
|
|
173
|
+
ALTER TABLE provider_state_new RENAME TO provider_state;
|
|
174
|
+
`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function recreatePendingConnections(db: Database) {
|
|
178
|
+
const rows = selectExisting(db, "pending_connections", [
|
|
179
|
+
"id",
|
|
180
|
+
"provider_id",
|
|
181
|
+
"auth_json",
|
|
182
|
+
"auth_type",
|
|
183
|
+
"source",
|
|
184
|
+
"captured_at",
|
|
185
|
+
"prompt_status",
|
|
186
|
+
]);
|
|
187
|
+
|
|
188
|
+
db.exec(`
|
|
189
|
+
DROP TABLE IF EXISTS pending_connections_new;
|
|
190
|
+
CREATE TABLE pending_connections_new (
|
|
191
|
+
id TEXT PRIMARY KEY,
|
|
192
|
+
provider_id TEXT NOT NULL,
|
|
193
|
+
auth_json TEXT NOT NULL,
|
|
194
|
+
auth_type TEXT NOT NULL,
|
|
195
|
+
source TEXT NOT NULL,
|
|
196
|
+
captured_at INTEGER NOT NULL,
|
|
197
|
+
prompt_status TEXT NOT NULL
|
|
198
|
+
);
|
|
199
|
+
`);
|
|
200
|
+
|
|
201
|
+
const insert = db.query<
|
|
202
|
+
unknown,
|
|
203
|
+
[string, string, string, string, string, number, string]
|
|
204
|
+
>(
|
|
205
|
+
`INSERT OR REPLACE INTO pending_connections_new (
|
|
206
|
+
id, provider_id, auth_json, auth_type, source, captured_at, prompt_status
|
|
207
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
208
|
+
);
|
|
209
|
+
for (const row of rows) {
|
|
210
|
+
if (
|
|
211
|
+
typeof row.id !== "string" ||
|
|
212
|
+
typeof row.provider_id !== "string" ||
|
|
213
|
+
typeof row.auth_json !== "string"
|
|
214
|
+
)
|
|
215
|
+
continue;
|
|
216
|
+
const authType =
|
|
217
|
+
typeof row.auth_type === "string" && authTypes.has(row.auth_type)
|
|
218
|
+
? row.auth_type
|
|
219
|
+
: inferAuthType(row.auth_json);
|
|
220
|
+
insert.run(
|
|
221
|
+
row.id,
|
|
222
|
+
row.provider_id,
|
|
223
|
+
row.auth_json,
|
|
224
|
+
authType,
|
|
225
|
+
typeof row.source === "string" ? row.source : "auth-file",
|
|
226
|
+
typeof row.captured_at === "number" ? row.captured_at : Date.now(),
|
|
227
|
+
typeof row.prompt_status === "string" ? row.prompt_status : "new",
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
db.exec(`
|
|
232
|
+
DROP TABLE IF EXISTS pending_connections;
|
|
233
|
+
ALTER TABLE pending_connections_new RENAME TO pending_connections;
|
|
234
|
+
`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function recreateUsageSnapshots(db: Database) {
|
|
238
|
+
const rows = selectExisting(db, "usage_snapshots", [
|
|
239
|
+
"provider_id",
|
|
240
|
+
"alias",
|
|
241
|
+
"fetched_at",
|
|
242
|
+
"confidence",
|
|
243
|
+
"normalized_json",
|
|
244
|
+
"raw_redacted_json",
|
|
245
|
+
"error",
|
|
246
|
+
]);
|
|
247
|
+
|
|
248
|
+
db.exec(`
|
|
249
|
+
DROP TABLE IF EXISTS usage_snapshots_new;
|
|
250
|
+
CREATE TABLE usage_snapshots_new (
|
|
251
|
+
provider_id TEXT NOT NULL,
|
|
252
|
+
alias TEXT NOT NULL,
|
|
253
|
+
fetched_at INTEGER NOT NULL,
|
|
254
|
+
confidence TEXT NOT NULL,
|
|
255
|
+
normalized_json TEXT NOT NULL,
|
|
256
|
+
raw_redacted_json TEXT,
|
|
257
|
+
error TEXT,
|
|
258
|
+
PRIMARY KEY (provider_id, alias)
|
|
259
|
+
);
|
|
260
|
+
`);
|
|
261
|
+
|
|
262
|
+
const insert = db.query<
|
|
263
|
+
unknown,
|
|
264
|
+
[string, string, number, string, string, string | null, string | null]
|
|
265
|
+
>(
|
|
266
|
+
`INSERT OR REPLACE INTO usage_snapshots_new (
|
|
267
|
+
provider_id, alias, fetched_at, confidence, normalized_json, raw_redacted_json, error
|
|
268
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
269
|
+
);
|
|
270
|
+
for (const row of rows) {
|
|
271
|
+
if (typeof row.provider_id !== "string" || typeof row.alias !== "string")
|
|
272
|
+
continue;
|
|
273
|
+
insert.run(
|
|
274
|
+
row.provider_id,
|
|
275
|
+
row.alias,
|
|
276
|
+
typeof row.fetched_at === "number" ? row.fetched_at : Date.now(),
|
|
277
|
+
typeof row.confidence === "string" ? row.confidence : "unavailable",
|
|
278
|
+
typeof row.normalized_json === "string" ? row.normalized_json : "{}",
|
|
279
|
+
typeof row.raw_redacted_json === "string" ? row.raw_redacted_json : null,
|
|
280
|
+
typeof row.error === "string" ? row.error : null,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
db.exec(`
|
|
285
|
+
DROP TABLE IF EXISTS usage_snapshots;
|
|
286
|
+
ALTER TABLE usage_snapshots_new RENAME TO usage_snapshots;
|
|
287
|
+
`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function recreateEvents(db: Database) {
|
|
291
|
+
const rows = selectExisting(db, "events", [
|
|
292
|
+
"id",
|
|
293
|
+
"type",
|
|
294
|
+
"provider_id",
|
|
295
|
+
"alias",
|
|
296
|
+
"message",
|
|
297
|
+
"created_at",
|
|
298
|
+
"metadata_json",
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
db.exec(`
|
|
302
|
+
DROP TABLE IF EXISTS events_new;
|
|
303
|
+
CREATE TABLE events_new (
|
|
304
|
+
id TEXT PRIMARY KEY,
|
|
305
|
+
type TEXT NOT NULL,
|
|
306
|
+
provider_id TEXT,
|
|
307
|
+
alias TEXT,
|
|
308
|
+
message TEXT NOT NULL,
|
|
309
|
+
created_at INTEGER NOT NULL,
|
|
310
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
311
|
+
);
|
|
312
|
+
`);
|
|
313
|
+
|
|
314
|
+
const insert = db.query<
|
|
315
|
+
unknown,
|
|
316
|
+
[string, string, string | null, string | null, string, number, string]
|
|
317
|
+
>(
|
|
318
|
+
`INSERT OR REPLACE INTO events_new (id, type, provider_id, alias, message, created_at, metadata_json)
|
|
319
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
320
|
+
);
|
|
321
|
+
for (const row of rows) {
|
|
322
|
+
if (
|
|
323
|
+
typeof row.id !== "string" ||
|
|
324
|
+
typeof row.type !== "string" ||
|
|
325
|
+
typeof row.message !== "string"
|
|
326
|
+
)
|
|
327
|
+
continue;
|
|
328
|
+
insert.run(
|
|
329
|
+
row.id,
|
|
330
|
+
row.type,
|
|
331
|
+
typeof row.provider_id === "string" ? row.provider_id : null,
|
|
332
|
+
typeof row.alias === "string" ? row.alias : null,
|
|
333
|
+
row.message,
|
|
334
|
+
typeof row.created_at === "number" ? row.created_at : Date.now(),
|
|
335
|
+
typeof row.metadata_json === "string" ? row.metadata_json : "{}",
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
db.exec(`
|
|
340
|
+
DROP TABLE IF EXISTS events;
|
|
341
|
+
ALTER TABLE events_new RENAME TO events;
|
|
342
|
+
`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function recreateProviderPriority(db: Database) {
|
|
346
|
+
const rows = selectExisting(db, "provider_priority", [
|
|
347
|
+
"provider_id",
|
|
348
|
+
"position",
|
|
349
|
+
"model_id",
|
|
350
|
+
"enabled",
|
|
351
|
+
"updated_at",
|
|
352
|
+
]);
|
|
353
|
+
|
|
354
|
+
db.exec(`
|
|
355
|
+
DROP TABLE IF EXISTS provider_priority_new;
|
|
356
|
+
CREATE TABLE provider_priority_new (
|
|
357
|
+
provider_id TEXT PRIMARY KEY,
|
|
358
|
+
position INTEGER NOT NULL,
|
|
359
|
+
model_id TEXT,
|
|
360
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
361
|
+
updated_at INTEGER NOT NULL
|
|
362
|
+
);
|
|
363
|
+
`);
|
|
364
|
+
|
|
365
|
+
const insert = db.query<
|
|
366
|
+
unknown,
|
|
367
|
+
[string, number, string | null, number, number]
|
|
368
|
+
>(
|
|
369
|
+
`INSERT OR REPLACE INTO provider_priority_new (provider_id, position, model_id, enabled, updated_at)
|
|
370
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
371
|
+
);
|
|
372
|
+
for (const row of rows) {
|
|
373
|
+
if (typeof row.provider_id !== "string") continue;
|
|
374
|
+
insert.run(
|
|
375
|
+
row.provider_id,
|
|
376
|
+
typeof row.position === "number" ? row.position : 0,
|
|
377
|
+
typeof row.model_id === "string" ? row.model_id : null,
|
|
378
|
+
row.enabled === 0 ? 0 : 1,
|
|
379
|
+
typeof row.updated_at === "number" ? row.updated_at : Date.now(),
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
db.exec(`
|
|
384
|
+
DROP TABLE IF EXISTS provider_priority;
|
|
385
|
+
ALTER TABLE provider_priority_new RENAME TO provider_priority;
|
|
386
|
+
`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function recreateSettings(db: Database) {
|
|
390
|
+
const rows = selectExisting(db, "settings", ["key", "value"]);
|
|
391
|
+
|
|
392
|
+
db.exec(`
|
|
393
|
+
DROP TABLE IF EXISTS settings_new;
|
|
394
|
+
CREATE TABLE settings_new (
|
|
395
|
+
key TEXT PRIMARY KEY,
|
|
396
|
+
value TEXT NOT NULL
|
|
397
|
+
);
|
|
398
|
+
`);
|
|
399
|
+
|
|
400
|
+
const insert = db.query<unknown, [string, string]>(
|
|
401
|
+
`INSERT OR REPLACE INTO settings_new (key, value) VALUES (?, ?)`,
|
|
402
|
+
);
|
|
403
|
+
for (const row of rows) {
|
|
404
|
+
if (typeof row.key !== "string" || typeof row.value !== "string") continue;
|
|
405
|
+
insert.run(row.key, row.value);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
db.exec(`
|
|
409
|
+
DROP TABLE IF EXISTS settings;
|
|
410
|
+
ALTER TABLE settings_new RENAME TO settings;
|
|
411
|
+
`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function migrate(db: Database) {
|
|
415
|
+
db.exec(`
|
|
416
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
417
|
+
version INTEGER PRIMARY KEY,
|
|
418
|
+
applied_at INTEGER NOT NULL
|
|
419
|
+
);
|
|
420
|
+
`);
|
|
421
|
+
|
|
422
|
+
const apply = db.transaction(() => {
|
|
423
|
+
recreateAccounts(db);
|
|
424
|
+
recreateProviderState(db);
|
|
425
|
+
recreatePendingConnections(db);
|
|
426
|
+
recreateUsageSnapshots(db);
|
|
427
|
+
recreateEvents(db);
|
|
428
|
+
recreateProviderPriority(db);
|
|
429
|
+
recreateSettings(db);
|
|
430
|
+
|
|
431
|
+
if (!hasMigration(db, 1)) {
|
|
432
|
+
db.query(
|
|
433
|
+
"INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)",
|
|
434
|
+
).run(1, Date.now());
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
apply();
|
|
439
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export type AuthInfo =
|
|
2
|
+
| { type: "api"; key: string; metadata?: Record<string, string> }
|
|
3
|
+
| {
|
|
4
|
+
type: "oauth";
|
|
5
|
+
refresh: string;
|
|
6
|
+
access: string;
|
|
7
|
+
expires: number;
|
|
8
|
+
accountId?: string;
|
|
9
|
+
enterpriseUrl?: string;
|
|
10
|
+
metadata?: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
| {
|
|
13
|
+
type: "wellknown";
|
|
14
|
+
key: string;
|
|
15
|
+
token: string;
|
|
16
|
+
metadata?: Record<string, string>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type Account = {
|
|
20
|
+
providerID: string;
|
|
21
|
+
alias: string;
|
|
22
|
+
auth: AuthInfo;
|
|
23
|
+
authType: AuthInfo["type"];
|
|
24
|
+
createdAt: number;
|
|
25
|
+
updatedAt: number;
|
|
26
|
+
lastUsedAt?: number;
|
|
27
|
+
rateLimitedUntil?: number;
|
|
28
|
+
failures: number;
|
|
29
|
+
disabled: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ProviderState = {
|
|
33
|
+
providerID: string;
|
|
34
|
+
activeAlias?: string;
|
|
35
|
+
updatedAt: number;
|
|
36
|
+
metadata: Record<string, string>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type SelectedModel = {
|
|
40
|
+
providerID: string;
|
|
41
|
+
modelID: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type PendingConnection = {
|
|
45
|
+
id: string;
|
|
46
|
+
providerID: string;
|
|
47
|
+
auth: AuthInfo;
|
|
48
|
+
authType: AuthInfo["type"];
|
|
49
|
+
source: "auth-file" | "http" | "oauth-callback";
|
|
50
|
+
capturedAt: number;
|
|
51
|
+
promptStatus: "new" | "prompted" | "dismissed";
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type BalancerEventType =
|
|
55
|
+
| "pending_connection_captured"
|
|
56
|
+
| "account_saved"
|
|
57
|
+
| "account_activated"
|
|
58
|
+
| "account_removed"
|
|
59
|
+
| "usage_refreshed"
|
|
60
|
+
| "usage_unavailable"
|
|
61
|
+
| "rate_limit_detected"
|
|
62
|
+
| "failover_performed";
|
|
63
|
+
|
|
64
|
+
export type BalancerEvent = {
|
|
65
|
+
id: string;
|
|
66
|
+
type: BalancerEventType;
|
|
67
|
+
providerID?: string;
|
|
68
|
+
alias?: string;
|
|
69
|
+
message: string;
|
|
70
|
+
createdAt: number;
|
|
71
|
+
metadata: Record<string, string>;
|
|
72
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Account } from "../types";
|
|
2
|
+
import { copilotUsageService } from "./providers/copilot";
|
|
3
|
+
import { openaiUsageService } from "./providers/openai";
|
|
4
|
+
import type { ProviderUsageSnapshot } from "./types";
|
|
5
|
+
|
|
6
|
+
export const usageServices = [openaiUsageService, copilotUsageService];
|
|
7
|
+
|
|
8
|
+
export async function refreshAccountUsage(
|
|
9
|
+
account: Account,
|
|
10
|
+
): Promise<ProviderUsageSnapshot> {
|
|
11
|
+
const service = usageServices.find((candidate) =>
|
|
12
|
+
candidate.supports(account.providerID),
|
|
13
|
+
);
|
|
14
|
+
if (service) return service.refreshUsage(account);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
alias: account.alias,
|
|
18
|
+
confidence: "unavailable",
|
|
19
|
+
fetchedAt: Date.now(),
|
|
20
|
+
message: `No usage service registered for ${account.providerID}.`,
|
|
21
|
+
providerID: account.providerID,
|
|
22
|
+
};
|
|
23
|
+
}
|