omnigateway 0.1.1 → 0.1.3
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/bin/omni.js +238 -111
- package/gateway.js +250 -58
- package/package.json +1 -1
- package/public/assets/{CopyValue-DrPV_Qao.js → CopyValue-bzKz2tiJ.js} +1 -1
- package/public/assets/{Modal-CXsglS9k.js → Modal-Crr8uyWt.js} +1 -1
- package/public/assets/{Rack-Be09hoVn.js → Rack-uDxrjvaz.js} +1 -1
- package/public/assets/{Toggle-Cd2Jy_Z1.js → Toggle-D0e4C94X.js} +1 -1
- package/public/assets/{_app-Bf5ZUmaT.js → _app-OSvB-Dv9.js} +1 -1
- package/public/assets/_app.accounts-BOE49WyS.js +51 -0
- package/public/assets/{_app.index-Ck_5mdeH.js → _app.index-CTRlqxOq.js} +1 -1
- package/public/assets/{_app.keys-C2XDmQJL.js → _app.keys-BNEOjtbu.js} +1 -1
- package/public/assets/{_app.logs-C2Xv-Izr.js → _app.logs-BxxqeEhM.js} +1 -1
- package/public/assets/{_app.models-DCxR_HKM.js → _app.models-BJi-RGUO.js} +6 -6
- package/public/assets/{_app.settings-SBc1Uaro.js → _app.settings-C1gB4w7c.js} +1 -1
- package/public/assets/_app.usage-B0iHKzmI.js +163 -0
- package/public/assets/catalog-DVRPlBJJ.js +1 -0
- package/public/assets/{index-JXZlD4dH.js → index--dA3FFSN.js} +2 -2
- package/public/assets/{login-CkrCC4MV.js → login-D-FsWNX8.js} +1 -1
- package/public/assets/trash-2-Cep_aVLx.js +1 -0
- package/public/index.html +1 -1
- package/public/assets/_app.accounts-BmFfVWsr.js +0 -51
- package/public/assets/_app.usage-BO3toUhx.js +0 -163
- package/public/assets/catalog-B1clfKRA.js +0 -1
package/bin/omni.js
CHANGED
|
@@ -608,6 +608,7 @@ async function* decodeAnthropic(messages) {
|
|
|
608
608
|
let cacheWriteTokens = 0;
|
|
609
609
|
let outputTokens = 0;
|
|
610
610
|
let stopReason = "endTurn";
|
|
611
|
+
let terminal = false;
|
|
611
612
|
for await (const msg of messages) {
|
|
612
613
|
const d = json(msg.data);
|
|
613
614
|
if (d === null)
|
|
@@ -673,6 +674,7 @@ async function* decodeAnthropic(messages) {
|
|
|
673
674
|
break;
|
|
674
675
|
}
|
|
675
676
|
case "message_stop":
|
|
677
|
+
terminal = true;
|
|
676
678
|
yield {
|
|
677
679
|
type: "end",
|
|
678
680
|
stopReason,
|
|
@@ -680,6 +682,7 @@ async function* decodeAnthropic(messages) {
|
|
|
680
682
|
};
|
|
681
683
|
break;
|
|
682
684
|
case "error": {
|
|
685
|
+
terminal = true;
|
|
683
686
|
const code = ERROR_TYPE[String(d.error?.type)] ?? "UPSTREAM";
|
|
684
687
|
yield {
|
|
685
688
|
type: "error",
|
|
@@ -693,6 +696,14 @@ async function* decodeAnthropic(messages) {
|
|
|
693
696
|
break;
|
|
694
697
|
}
|
|
695
698
|
}
|
|
699
|
+
if (!terminal) {
|
|
700
|
+
yield {
|
|
701
|
+
type: "error",
|
|
702
|
+
code: "UPSTREAM",
|
|
703
|
+
message: "upstream stream ended before message_stop",
|
|
704
|
+
retryable: RETRYABLE.UPSTREAM
|
|
705
|
+
};
|
|
706
|
+
}
|
|
696
707
|
}
|
|
697
708
|
|
|
698
709
|
// packages/providers/src/anthropic/wire.ts
|
|
@@ -805,9 +816,10 @@ var anthropicAdapter = {
|
|
|
805
816
|
["anthropic-version", API_VERSION],
|
|
806
817
|
["Accept", req.request.stream ? "text/event-stream" : "application/json"]
|
|
807
818
|
];
|
|
819
|
+
const betas = new Set(req.request.betas ?? []);
|
|
808
820
|
if (oauth) {
|
|
809
821
|
protocol.push(["Authorization", `Bearer ${req.credentials.accessToken}`]);
|
|
810
|
-
|
|
822
|
+
betas.add(OAUTH_BETA);
|
|
811
823
|
} else if (req.credentials.apiKey !== null) {
|
|
812
824
|
protocol.push(["x-api-key", req.credentials.apiKey]);
|
|
813
825
|
} else {
|
|
@@ -815,6 +827,8 @@ var anthropicAdapter = {
|
|
|
815
827
|
provider: "anthropic"
|
|
816
828
|
});
|
|
817
829
|
}
|
|
830
|
+
if (betas.size > 0)
|
|
831
|
+
protocol.push(["anthropic-beta", [...betas].join(",")]);
|
|
818
832
|
const profile = PROFILES.anthropic;
|
|
819
833
|
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
820
834
|
const bodyString = signAnthropicBody(JSON.stringify(orderFields(withSystem, BODY_ORDER.anthropic)));
|
|
@@ -1032,18 +1046,16 @@ async function* decodeChat(messages) {
|
|
|
1032
1046
|
let started = false;
|
|
1033
1047
|
let textOpen = false;
|
|
1034
1048
|
let textIndex;
|
|
1035
|
-
let
|
|
1049
|
+
let done = false;
|
|
1036
1050
|
let stopReason = "endTurn";
|
|
1037
1051
|
let usage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
|
|
1038
1052
|
const toolIndex = new Map;
|
|
1039
1053
|
let nextIndex = 0;
|
|
1040
|
-
const emitEnd = () => {
|
|
1041
|
-
ended = true;
|
|
1042
|
-
return { type: "end", stopReason, usage };
|
|
1043
|
-
};
|
|
1044
1054
|
for await (const msg of messages) {
|
|
1045
|
-
if (msg.data === "[DONE]")
|
|
1055
|
+
if (msg.data === "[DONE]") {
|
|
1056
|
+
done = true;
|
|
1046
1057
|
break;
|
|
1058
|
+
}
|
|
1047
1059
|
const d = json2(msg.data);
|
|
1048
1060
|
if (d === null)
|
|
1049
1061
|
continue;
|
|
@@ -1098,19 +1110,21 @@ async function* decodeChat(messages) {
|
|
|
1098
1110
|
}
|
|
1099
1111
|
if (typeof choice.finish_reason === "string") {
|
|
1100
1112
|
stopReason = FINISH[choice.finish_reason] ?? "endTurn";
|
|
1101
|
-
if (textOpen)
|
|
1102
|
-
yield { type: "blockEnd", index: textIndex ?? 0 };
|
|
1103
|
-
for (const index of toolIndex.values())
|
|
1104
|
-
yield { type: "blockEnd", index };
|
|
1105
|
-
yield emitEnd();
|
|
1106
1113
|
}
|
|
1107
1114
|
}
|
|
1108
|
-
if (
|
|
1115
|
+
if (done) {
|
|
1109
1116
|
if (textOpen)
|
|
1110
1117
|
yield { type: "blockEnd", index: textIndex ?? 0 };
|
|
1111
1118
|
for (const index of toolIndex.values())
|
|
1112
1119
|
yield { type: "blockEnd", index };
|
|
1113
|
-
yield
|
|
1120
|
+
yield { type: "end", stopReason, usage };
|
|
1121
|
+
} else {
|
|
1122
|
+
yield {
|
|
1123
|
+
type: "error",
|
|
1124
|
+
code: "UPSTREAM",
|
|
1125
|
+
message: "upstream stream ended before [DONE]",
|
|
1126
|
+
retryable: RETRYABLE.UPSTREAM
|
|
1127
|
+
};
|
|
1114
1128
|
}
|
|
1115
1129
|
}
|
|
1116
1130
|
|
|
@@ -1265,6 +1279,7 @@ async function* decodeResponses(messages) {
|
|
|
1265
1279
|
return assigned;
|
|
1266
1280
|
};
|
|
1267
1281
|
let sawToolCall = false;
|
|
1282
|
+
let terminal = false;
|
|
1268
1283
|
const ownsBlock = new Set;
|
|
1269
1284
|
for await (const msg of messages) {
|
|
1270
1285
|
const d = json3(msg.data);
|
|
@@ -1340,6 +1355,7 @@ async function* decodeResponses(messages) {
|
|
|
1340
1355
|
}
|
|
1341
1356
|
case "response.completed":
|
|
1342
1357
|
case "response.incomplete": {
|
|
1358
|
+
terminal = true;
|
|
1343
1359
|
const r = d.response ?? {};
|
|
1344
1360
|
const reason = r.incomplete_details?.reason;
|
|
1345
1361
|
let stopReason = sawToolCall ? "toolUse" : "endTurn";
|
|
@@ -1361,6 +1377,7 @@ async function* decodeResponses(messages) {
|
|
|
1361
1377
|
}
|
|
1362
1378
|
case "response.failed":
|
|
1363
1379
|
case "error": {
|
|
1380
|
+
terminal = true;
|
|
1364
1381
|
const err = d.response?.error ?? d.error ?? {};
|
|
1365
1382
|
const code = ERROR_CODE[String(err.code ?? err.type)] ?? "UPSTREAM";
|
|
1366
1383
|
yield {
|
|
@@ -1375,6 +1392,14 @@ async function* decodeResponses(messages) {
|
|
|
1375
1392
|
break;
|
|
1376
1393
|
}
|
|
1377
1394
|
}
|
|
1395
|
+
if (!terminal) {
|
|
1396
|
+
yield {
|
|
1397
|
+
type: "error",
|
|
1398
|
+
code: "UPSTREAM",
|
|
1399
|
+
message: "upstream stream ended before response completion",
|
|
1400
|
+
retryable: RETRYABLE.UPSTREAM
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1378
1403
|
}
|
|
1379
1404
|
|
|
1380
1405
|
// packages/providers/src/openai/wire.ts
|
|
@@ -16415,25 +16440,104 @@ function requireDimension(grain, dimension) {
|
|
|
16415
16440
|
}
|
|
16416
16441
|
|
|
16417
16442
|
// packages/control/src/credentials.ts
|
|
16443
|
+
function summarizeCredential(credential) {
|
|
16444
|
+
return {
|
|
16445
|
+
id: credential.id,
|
|
16446
|
+
provider: credential.provider,
|
|
16447
|
+
label: credential.label,
|
|
16448
|
+
authType: credential.authType,
|
|
16449
|
+
enabled: credential.enabled,
|
|
16450
|
+
tier: credential.tier,
|
|
16451
|
+
weight: credential.weight,
|
|
16452
|
+
expiresAt: credential.expiresAt,
|
|
16453
|
+
accountEmail: credential.accountEmail,
|
|
16454
|
+
providerData: credential.providerData,
|
|
16455
|
+
disabledReason: credential.disabledReason,
|
|
16456
|
+
disabledAt: credential.disabledAt,
|
|
16457
|
+
hasRefreshToken: credential.hasRefreshToken,
|
|
16458
|
+
createdAt: credential.createdAt,
|
|
16459
|
+
updatedAt: credential.updatedAt
|
|
16460
|
+
};
|
|
16461
|
+
}
|
|
16418
16462
|
async function listCredentials(store) {
|
|
16419
|
-
|
|
16420
|
-
|
|
16421
|
-
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
|
|
16425
|
-
|
|
16426
|
-
|
|
16427
|
-
|
|
16428
|
-
|
|
16429
|
-
|
|
16430
|
-
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
|
|
16435
|
-
|
|
16436
|
-
|
|
16463
|
+
return (await store.credentials.list()).map(summarizeCredential);
|
|
16464
|
+
}
|
|
16465
|
+
async function getCredential(store, id) {
|
|
16466
|
+
const credential = await store.credentials.get(id);
|
|
16467
|
+
if (credential === null)
|
|
16468
|
+
throw new GatewayError("BAD_REQUEST", "no such credential");
|
|
16469
|
+
return summarizeCredential(credential);
|
|
16470
|
+
}
|
|
16471
|
+
async function createApiKeyCredential(store, input) {
|
|
16472
|
+
const provider = parseOrThrow(providerIdSchema, input.provider);
|
|
16473
|
+
if (typeof input.apiKey !== "string" || input.apiKey.trim().length === 0) {
|
|
16474
|
+
throw new GatewayError("BAD_REQUEST", "apiKey: must not be empty");
|
|
16475
|
+
}
|
|
16476
|
+
if (input.label !== undefined && typeof input.label !== "string") {
|
|
16477
|
+
throw new GatewayError("BAD_REQUEST", "label: must be a string");
|
|
16478
|
+
}
|
|
16479
|
+
const label = input.label?.trim() || `${provider} api key`;
|
|
16480
|
+
const created = await store.credentials.create({
|
|
16481
|
+
id: crypto.randomUUID(),
|
|
16482
|
+
provider,
|
|
16483
|
+
label,
|
|
16484
|
+
authType: "apiKey",
|
|
16485
|
+
enabled: true,
|
|
16486
|
+
tier: 1,
|
|
16487
|
+
weight: 1,
|
|
16488
|
+
expiresAt: null,
|
|
16489
|
+
accountEmail: null,
|
|
16490
|
+
providerData: {},
|
|
16491
|
+
disabledReason: null,
|
|
16492
|
+
disabledAt: null,
|
|
16493
|
+
accessToken: null,
|
|
16494
|
+
refreshToken: null,
|
|
16495
|
+
apiKey: input.apiKey,
|
|
16496
|
+
idToken: null
|
|
16497
|
+
});
|
|
16498
|
+
return summarizeCredential(created);
|
|
16499
|
+
}
|
|
16500
|
+
async function refreshCredential(deps, id) {
|
|
16501
|
+
const credential = await deps.store.credentials.get(id);
|
|
16502
|
+
if (credential === null)
|
|
16503
|
+
throw new GatewayError("BAD_REQUEST", "no such credential");
|
|
16504
|
+
if (credential.authType !== "oauth") {
|
|
16505
|
+
throw new GatewayError("BAD_REQUEST", `credential "${id}" is an api key and has nothing to refresh`);
|
|
16506
|
+
}
|
|
16507
|
+
await deps.refresh(credential);
|
|
16508
|
+
return getCredential(deps.store, id);
|
|
16509
|
+
}
|
|
16510
|
+
async function credentialHealth(store) {
|
|
16511
|
+
const [health, quota] = await Promise.all([
|
|
16512
|
+
store.credentials.listHealth(),
|
|
16513
|
+
store.credentials.listQuota()
|
|
16514
|
+
]);
|
|
16515
|
+
return { health, quota };
|
|
16516
|
+
}
|
|
16517
|
+
async function credentialStatus(store, options) {
|
|
16518
|
+
const [credentials, quota, adminConfigured] = await Promise.all([
|
|
16519
|
+
listCredentials(store),
|
|
16520
|
+
store.credentials.listQuota(),
|
|
16521
|
+
createAdminAuth(store, { now: options.now, sessionTtlMs: 0 }).isConfigured()
|
|
16522
|
+
]);
|
|
16523
|
+
const byCredential = new Map;
|
|
16524
|
+
for (const row of quota) {
|
|
16525
|
+
const rows = byCredential.get(row.credentialId);
|
|
16526
|
+
if (rows === undefined)
|
|
16527
|
+
byCredential.set(row.credentialId, [row]);
|
|
16528
|
+
else
|
|
16529
|
+
rows.push(row);
|
|
16530
|
+
}
|
|
16531
|
+
return {
|
|
16532
|
+
adminConfigured,
|
|
16533
|
+
credentials: credentials.map(({ id, provider, label, enabled }) => ({
|
|
16534
|
+
id,
|
|
16535
|
+
provider,
|
|
16536
|
+
label,
|
|
16537
|
+
enabled,
|
|
16538
|
+
quota: byCredential.get(id) ?? []
|
|
16539
|
+
}))
|
|
16540
|
+
};
|
|
16437
16541
|
}
|
|
16438
16542
|
async function patchCredential(deps, id, input) {
|
|
16439
16543
|
const patch = parseOrThrow(credentialPatchSchema, input);
|
|
@@ -16461,7 +16565,7 @@ function healthKey(credentialId, model) {
|
|
|
16461
16565
|
}
|
|
16462
16566
|
async function buildSnapshot(store, now) {
|
|
16463
16567
|
const [credentials, healthRows, quotaRows, models, settings] = await Promise.all([
|
|
16464
|
-
store.credentials.
|
|
16568
|
+
store.credentials.listRouting(),
|
|
16465
16569
|
store.credentials.listHealth(),
|
|
16466
16570
|
store.credentials.listQuota(),
|
|
16467
16571
|
store.config.listModels(),
|
|
@@ -16775,7 +16879,7 @@ var DEFAULT_SETTINGS = {
|
|
|
16775
16879
|
// packages/store/src/sqlite/config.ts
|
|
16776
16880
|
var SETTINGS_KEY = "settings";
|
|
16777
16881
|
var ADMIN_HASH_KEY = "adminPasswordHash";
|
|
16778
|
-
function createConfigRepo(db) {
|
|
16882
|
+
function createConfigRepo(db, emit2 = () => {}) {
|
|
16779
16883
|
const readRaw = (key) => db.query("SELECT value FROM settings WHERE key = ?").get(key)?.value ?? null;
|
|
16780
16884
|
const writeRaw = (key, value) => {
|
|
16781
16885
|
db.run("INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value", [key, value]);
|
|
@@ -16795,9 +16899,11 @@ function createConfigRepo(db) {
|
|
|
16795
16899
|
targets = excluded.targets,
|
|
16796
16900
|
strategy = excluded.strategy,
|
|
16797
16901
|
is_alias = excluded.is_alias`, [model.id, JSON.stringify(model.targets), model.strategy, model.isAlias ? 1 : 0]);
|
|
16902
|
+
emit2({ type: "modelsChanged" });
|
|
16798
16903
|
},
|
|
16799
16904
|
async removeModel(id) {
|
|
16800
16905
|
db.run("DELETE FROM virtual_models WHERE id = ?", [id]);
|
|
16906
|
+
emit2({ type: "modelsChanged" });
|
|
16801
16907
|
},
|
|
16802
16908
|
async getSettings() {
|
|
16803
16909
|
const raw = readRaw(SETTINGS_KEY);
|
|
@@ -16818,6 +16924,7 @@ function createConfigRepo(db) {
|
|
|
16818
16924
|
weights: { ...current.weights, ...patch.weights }
|
|
16819
16925
|
};
|
|
16820
16926
|
writeRaw(SETTINGS_KEY, JSON.stringify(next));
|
|
16927
|
+
emit2({ type: "settingsChanged" });
|
|
16821
16928
|
return next;
|
|
16822
16929
|
},
|
|
16823
16930
|
async getAdminPasswordHash() {
|
|
@@ -16833,8 +16940,40 @@ function createConfigRepo(db) {
|
|
|
16833
16940
|
};
|
|
16834
16941
|
}
|
|
16835
16942
|
// packages/store/src/sqlite/credentials.ts
|
|
16836
|
-
function createCredentialRepo(db, key) {
|
|
16837
|
-
const
|
|
16943
|
+
function createCredentialRepo(db, key, emit2 = () => {}) {
|
|
16944
|
+
const open = async (v) => v === null ? null : decrypt(key, v);
|
|
16945
|
+
const secretsFrom = async (row) => ({
|
|
16946
|
+
accessToken: await open(row.access_token),
|
|
16947
|
+
refreshToken: await open(row.refresh_token),
|
|
16948
|
+
apiKey: await open(row.api_key),
|
|
16949
|
+
idToken: await open(row.id_token)
|
|
16950
|
+
});
|
|
16951
|
+
const requiredRow = (row, id) => {
|
|
16952
|
+
if (row === null)
|
|
16953
|
+
throw new Error(`credential ${id} no longer exists`);
|
|
16954
|
+
return row;
|
|
16955
|
+
};
|
|
16956
|
+
const currentSecrets = async (id) => {
|
|
16957
|
+
const row = requiredRow(db.query("SELECT access_token, refresh_token, api_key, id_token FROM credentials WHERE id = ?").get(id), id);
|
|
16958
|
+
return secretsFrom(row);
|
|
16959
|
+
};
|
|
16960
|
+
const currentInferenceSecrets = async (id, authType) => {
|
|
16961
|
+
if (authType === "oauth") {
|
|
16962
|
+
const row2 = requiredRow(db.query("SELECT access_token FROM credentials WHERE id = ?").get(id), id);
|
|
16963
|
+
return { accessToken: await open(row2.access_token), apiKey: null };
|
|
16964
|
+
}
|
|
16965
|
+
const row = requiredRow(db.query("SELECT api_key FROM credentials WHERE id = ?").get(id), id);
|
|
16966
|
+
return { accessToken: null, apiKey: await open(row.api_key) };
|
|
16967
|
+
};
|
|
16968
|
+
const currentRefreshSecrets = async (id) => {
|
|
16969
|
+
const row = requiredRow(db.query("SELECT refresh_token FROM credentials WHERE id = ?").get(id), id);
|
|
16970
|
+
return { refreshToken: await open(row.refresh_token) };
|
|
16971
|
+
};
|
|
16972
|
+
const currentUsageSecrets = async (id) => {
|
|
16973
|
+
const row = requiredRow(db.query("SELECT access_token FROM credentials WHERE id = ?").get(id), id);
|
|
16974
|
+
return { accessToken: await open(row.access_token) };
|
|
16975
|
+
};
|
|
16976
|
+
const view = (row, loadCurrentSecrets = false) => ({
|
|
16838
16977
|
id: row.id,
|
|
16839
16978
|
provider: row.provider,
|
|
16840
16979
|
label: row.label,
|
|
@@ -16850,18 +16989,31 @@ function createCredentialRepo(db, key) {
|
|
|
16850
16989
|
hasRefreshToken: row.refresh_token !== null,
|
|
16851
16990
|
createdAt: row.created_at,
|
|
16852
16991
|
updatedAt: row.updated_at,
|
|
16853
|
-
secrets:
|
|
16854
|
-
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
|
|
16858
|
-
|
|
16992
|
+
secrets: () => loadCurrentSecrets ? currentSecrets(row.id) : secretsFrom(row),
|
|
16993
|
+
openForInference: async () => {
|
|
16994
|
+
if (loadCurrentSecrets)
|
|
16995
|
+
return currentInferenceSecrets(row.id, row.auth_type);
|
|
16996
|
+
if (row.auth_type === "oauth") {
|
|
16997
|
+
return { accessToken: await open(row.access_token), apiKey: null };
|
|
16998
|
+
}
|
|
16999
|
+
return { accessToken: null, apiKey: await open(row.api_key) };
|
|
17000
|
+
},
|
|
17001
|
+
openForRefresh: async () => loadCurrentSecrets ? currentRefreshSecrets(row.id) : { refreshToken: await open(row.refresh_token) },
|
|
17002
|
+
openForUsage: async () => loadCurrentSecrets ? currentUsageSecrets(row.id) : { accessToken: await open(row.access_token) }
|
|
16859
17003
|
});
|
|
16860
|
-
const open = async (v) => v === null ? null : decrypt(key, v);
|
|
16861
17004
|
const seal = async (v) => v === null || v === undefined ? null : encrypt(key, v);
|
|
16862
17005
|
return {
|
|
16863
17006
|
async list() {
|
|
16864
|
-
return db.query("SELECT * FROM credentials ORDER BY tier, label").all().map(view);
|
|
17007
|
+
return db.query("SELECT * FROM credentials ORDER BY tier, label").all().map((row) => view(row));
|
|
17008
|
+
},
|
|
17009
|
+
async listRouting() {
|
|
17010
|
+
return db.query(`SELECT id, provider, label, auth_type, enabled, tier, weight, expires_at,
|
|
17011
|
+
account_email, provider_data, disabled_reason, disabled_at,
|
|
17012
|
+
NULL AS access_token,
|
|
17013
|
+
CASE WHEN refresh_token IS NULL THEN NULL ELSE 'present' END AS refresh_token,
|
|
17014
|
+
NULL AS api_key, NULL AS id_token, created_at, updated_at
|
|
17015
|
+
FROM credentials
|
|
17016
|
+
ORDER BY tier, label`).all().map((row) => view(row, true));
|
|
16865
17017
|
},
|
|
16866
17018
|
async get(id) {
|
|
16867
17019
|
const row = db.query("SELECT * FROM credentials WHERE id = ?").get(id);
|
|
@@ -16894,6 +17046,7 @@ function createCredentialRepo(db, key) {
|
|
|
16894
17046
|
now
|
|
16895
17047
|
]);
|
|
16896
17048
|
const { accessToken, refreshToken, apiKey, idToken, ...meta3 } = input;
|
|
17049
|
+
emit2({ type: "credentialsChanged" });
|
|
16897
17050
|
return {
|
|
16898
17051
|
...meta3,
|
|
16899
17052
|
hasRefreshToken: refreshToken != null,
|
|
@@ -16930,6 +17083,7 @@ function createCredentialRepo(db, key) {
|
|
|
16930
17083
|
return;
|
|
16931
17084
|
put("updated_at", Date.now());
|
|
16932
17085
|
db.run(`UPDATE credentials SET ${sets.join(", ")} WHERE id = ?`, [...vals, id]);
|
|
17086
|
+
emit2({ type: "credentialsChanged" });
|
|
16933
17087
|
},
|
|
16934
17088
|
async updateSecrets(id, secrets, expiresAt) {
|
|
16935
17089
|
const sets = [];
|
|
@@ -16953,9 +17107,11 @@ function createCredentialRepo(db, key) {
|
|
|
16953
17107
|
sets.push("expires_at = ?", "updated_at = ?");
|
|
16954
17108
|
vals.push(expiresAt, Date.now());
|
|
16955
17109
|
db.run(`UPDATE credentials SET ${sets.join(", ")} WHERE id = ?`, [...vals, id]);
|
|
17110
|
+
emit2({ type: "credentialsChanged" });
|
|
16956
17111
|
},
|
|
16957
17112
|
async remove(id) {
|
|
16958
17113
|
db.run("DELETE FROM credentials WHERE id = ?", [id]);
|
|
17114
|
+
emit2({ type: "credentialsChanged" });
|
|
16959
17115
|
},
|
|
16960
17116
|
async listHealth() {
|
|
16961
17117
|
return db.query("SELECT * FROM credential_health").all().map((r) => ({
|
|
@@ -16986,6 +17142,7 @@ function createCredentialRepo(db, key) {
|
|
|
16986
17142
|
stmt.run(r.credentialId, r.model, r.breakerState, r.consecutiveFailures, r.openedAt, r.rateLimitedUntil, r.ewmaTtftMs, r.lastUsedAt);
|
|
16987
17143
|
}
|
|
16988
17144
|
})();
|
|
17145
|
+
emit2({ type: "healthSaved", rows });
|
|
16989
17146
|
},
|
|
16990
17147
|
async listQuota() {
|
|
16991
17148
|
return db.query("SELECT * FROM quota_windows").all().map((r) => ({
|
|
@@ -17025,6 +17182,7 @@ function createCredentialRepo(db, key) {
|
|
|
17025
17182
|
prune.run(credentialId, JSON.stringify(types2));
|
|
17026
17183
|
}
|
|
17027
17184
|
})();
|
|
17185
|
+
emit2({ type: "quotaSaved", rows });
|
|
17028
17186
|
}
|
|
17029
17187
|
};
|
|
17030
17188
|
}
|
|
@@ -17497,11 +17655,26 @@ function createUsageRepo(db) {
|
|
|
17497
17655
|
// packages/store/src/sqlite/store.ts
|
|
17498
17656
|
async function createStore(opts) {
|
|
17499
17657
|
const db = openDb(opts.path);
|
|
17658
|
+
const listeners = new Set;
|
|
17659
|
+
const emit2 = (change) => {
|
|
17660
|
+
for (const listener of listeners) {
|
|
17661
|
+
try {
|
|
17662
|
+
listener(change);
|
|
17663
|
+
} catch {}
|
|
17664
|
+
}
|
|
17665
|
+
};
|
|
17500
17666
|
return {
|
|
17501
|
-
credentials: createCredentialRepo(db, opts.encryptionKey),
|
|
17502
|
-
config: createConfigRepo(db),
|
|
17667
|
+
credentials: createCredentialRepo(db, opts.encryptionKey, emit2),
|
|
17668
|
+
config: createConfigRepo(db, emit2),
|
|
17503
17669
|
keys: createKeyRepo(db),
|
|
17504
17670
|
usage: createUsageRepo(db),
|
|
17671
|
+
routing: {
|
|
17672
|
+
version: () => db.query("PRAGMA data_version").get()?.data_version ?? 0,
|
|
17673
|
+
subscribe(listener) {
|
|
17674
|
+
listeners.add(listener);
|
|
17675
|
+
return () => listeners.delete(listener);
|
|
17676
|
+
}
|
|
17677
|
+
},
|
|
17505
17678
|
close: () => db.close()
|
|
17506
17679
|
};
|
|
17507
17680
|
}
|
|
@@ -17829,7 +18002,7 @@ var OAUTH_PROVIDERS = {
|
|
|
17829
18002
|
function createRefresher(deps) {
|
|
17830
18003
|
const inFlight = new Map;
|
|
17831
18004
|
async function run(credential) {
|
|
17832
|
-
const secrets = await credential.
|
|
18005
|
+
const secrets = await credential.openForRefresh();
|
|
17833
18006
|
if (secrets.refreshToken === null) {
|
|
17834
18007
|
throw new GatewayError("AUTH", `credential ${credential.id} has no refresh token`);
|
|
17835
18008
|
}
|
|
@@ -18166,10 +18339,7 @@ var credentialsList = {
|
|
|
18166
18339
|
}
|
|
18167
18340
|
};
|
|
18168
18341
|
async function findCredential(ctx, id) {
|
|
18169
|
-
|
|
18170
|
-
if (credential === null)
|
|
18171
|
-
throw new CliError(`no credential "${id}"`);
|
|
18172
|
-
return credential;
|
|
18342
|
+
return getCredential(await ctx.store(), id);
|
|
18173
18343
|
}
|
|
18174
18344
|
var credentialsShow = {
|
|
18175
18345
|
usage: "credentials show <id>",
|
|
@@ -18268,12 +18438,7 @@ var credentialsRefresh = {
|
|
|
18268
18438
|
async run(args, { ctx, writer }) {
|
|
18269
18439
|
const id = requirePositional(args, 0, "credential id");
|
|
18270
18440
|
const store = await ctx.store();
|
|
18271
|
-
const credential = await
|
|
18272
|
-
if (credential === null)
|
|
18273
|
-
throw new CliError(`no credential "${id}"`);
|
|
18274
|
-
if (credential.authType !== "oauth") {
|
|
18275
|
-
throw new CliError(`credential "${id}" is an api key and has nothing to refresh`);
|
|
18276
|
-
}
|
|
18441
|
+
const credential = await findCredential(ctx, id);
|
|
18277
18442
|
const refresh = createRefresher({
|
|
18278
18443
|
store,
|
|
18279
18444
|
providers: OAUTH_PROVIDERS,
|
|
@@ -18281,9 +18446,8 @@ var credentialsRefresh = {
|
|
|
18281
18446
|
now: ctx.now
|
|
18282
18447
|
});
|
|
18283
18448
|
note(ctx, writer, `refreshing ${credential.provider} credential ${id}\u2026`);
|
|
18284
|
-
await refresh
|
|
18285
|
-
|
|
18286
|
-
emit(ctx, writer, { id, expiresAt: updated?.expiresAt ?? null }, () => `${id} refreshed; expires ${formatTime(updated?.expiresAt ?? null)}`);
|
|
18449
|
+
const updated = await refreshCredential({ store, refresh }, id);
|
|
18450
|
+
emit(ctx, writer, { id, expiresAt: updated.expiresAt }, () => `${id} refreshed; expires ${formatTime(updated.expiresAt)}`);
|
|
18287
18451
|
}
|
|
18288
18452
|
};
|
|
18289
18453
|
var credentialsAddKey = {
|
|
@@ -18298,27 +18462,12 @@ var credentialsAddKey = {
|
|
|
18298
18462
|
const key = await prompt.secret(`${providerId} API key: `);
|
|
18299
18463
|
if (key.length === 0)
|
|
18300
18464
|
throw new CliError("no API key given");
|
|
18301
|
-
const
|
|
18302
|
-
const id = crypto.randomUUID();
|
|
18303
|
-
await store.credentials.create({
|
|
18304
|
-
id,
|
|
18465
|
+
const created = await createApiKeyCredential(await ctx.store(), {
|
|
18305
18466
|
provider: providerId,
|
|
18306
|
-
label: stringFlag(args.values, "label") ?? `${providerId} api key`,
|
|
18307
|
-
authType: "apiKey",
|
|
18308
|
-
enabled: true,
|
|
18309
|
-
tier: 1,
|
|
18310
|
-
weight: 1,
|
|
18311
|
-
expiresAt: null,
|
|
18312
|
-
accountEmail: null,
|
|
18313
|
-
providerData: {},
|
|
18314
|
-
disabledReason: null,
|
|
18315
|
-
disabledAt: null,
|
|
18316
|
-
accessToken: null,
|
|
18317
|
-
refreshToken: null,
|
|
18318
18467
|
apiKey: key,
|
|
18319
|
-
|
|
18468
|
+
label: stringFlag(args.values, "label")
|
|
18320
18469
|
});
|
|
18321
|
-
emit(ctx, writer, { id, provider:
|
|
18470
|
+
emit(ctx, writer, { id: created.id, provider: created.provider }, () => `stored ${created.provider} api key as ${created.id}`);
|
|
18322
18471
|
}
|
|
18323
18472
|
};
|
|
18324
18473
|
var credentialsHealth = {
|
|
@@ -18327,8 +18476,8 @@ var credentialsHealth = {
|
|
|
18327
18476
|
options: { all: { type: "boolean" } },
|
|
18328
18477
|
async run(args, { ctx, writer }) {
|
|
18329
18478
|
const store = await ctx.store();
|
|
18330
|
-
const [rows, credentials] = await Promise.all([
|
|
18331
|
-
store
|
|
18479
|
+
const [{ health: rows }, credentials] = await Promise.all([
|
|
18480
|
+
credentialHealth(store),
|
|
18332
18481
|
listCredentials(store)
|
|
18333
18482
|
]);
|
|
18334
18483
|
const labels = new Map(credentials.map((c) => [c.id, c.label]));
|
|
@@ -19167,30 +19316,11 @@ var status2 = {
|
|
|
19167
19316
|
} catch (error51) {
|
|
19168
19317
|
storeError = error51 instanceof Error ? error51.message : "could not open the database";
|
|
19169
19318
|
}
|
|
19170
|
-
const
|
|
19171
|
-
const
|
|
19172
|
-
const configured = store === null ? false : await createAdminAuth(store, {
|
|
19173
|
-
now: ctx.now,
|
|
19174
|
-
sessionTtlMs: 0
|
|
19175
|
-
}).isConfigured();
|
|
19176
|
-
const byCredential = new Map;
|
|
19177
|
-
for (const row of quotaRows) {
|
|
19178
|
-
const list = byCredential.get(row.credentialId);
|
|
19179
|
-
if (list === undefined)
|
|
19180
|
-
byCredential.set(row.credentialId, [row]);
|
|
19181
|
-
else
|
|
19182
|
-
list.push(row);
|
|
19183
|
-
}
|
|
19319
|
+
const persistent = store === null ? { adminConfigured: false, credentials: [] } : await credentialStatus(store, { now: ctx.now });
|
|
19320
|
+
const { adminConfigured: configured, credentials } = persistent;
|
|
19184
19321
|
const data = {
|
|
19185
19322
|
process: process3,
|
|
19186
|
-
|
|
19187
|
-
credentials: credentials.map((credential) => ({
|
|
19188
|
-
id: credential.id,
|
|
19189
|
-
provider: credential.provider,
|
|
19190
|
-
label: credential.label,
|
|
19191
|
-
enabled: credential.enabled,
|
|
19192
|
-
quota: byCredential.get(credential.id) ?? []
|
|
19193
|
-
})),
|
|
19323
|
+
...persistent,
|
|
19194
19324
|
storeError
|
|
19195
19325
|
};
|
|
19196
19326
|
emit(ctx, writer, data, () => {
|
|
@@ -19212,15 +19342,12 @@ ${state(ctx, false, storeError)}`;
|
|
|
19212
19342
|
|
|
19213
19343
|
no credentials; add one with: omni connect <provider>`;
|
|
19214
19344
|
}
|
|
19215
|
-
const rows = credentials.map((credential) =>
|
|
19216
|
-
|
|
19217
|
-
|
|
19218
|
-
|
|
19219
|
-
|
|
19220
|
-
|
|
19221
|
-
quotaCell(ctx, windows, ctx.now())
|
|
19222
|
-
];
|
|
19223
|
-
});
|
|
19345
|
+
const rows = credentials.map((credential) => [
|
|
19346
|
+
credential.label,
|
|
19347
|
+
provider(ctx, credential.provider),
|
|
19348
|
+
state(ctx, credential.enabled, credential.enabled ? "enabled" : "disabled"),
|
|
19349
|
+
quotaCell(ctx, credential.quota, ctx.now())
|
|
19350
|
+
]);
|
|
19224
19351
|
return `${header}
|
|
19225
19352
|
|
|
19226
19353
|
${table([{ header: "ACCOUNT" }, { header: "PROVIDER" }, { header: "STATE" }, { header: "QUOTA" }], rows)}`;
|