omnigateway 0.11.0 → 0.11.2
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/README.md +16 -17
- package/bin/omni.js +184 -35
- package/gateway.js +139 -40
- package/package.json +2 -2
- package/public/assets/{Chip-CmL-9rw7.js → Chip-77Q04h7S.js} +2 -1
- package/public/assets/{CopyValue-CTVbU0O_.js → CopyValue-DDJId-Zo.js} +1 -1
- package/public/assets/{Field-DcNu4yw-.js → Field-56pmSGrh.js} +1 -1
- package/public/assets/{Lamp-DtHKJH6P.js → Lamp-Drd1fFwQ.js} +7 -7
- package/public/assets/{Panel-BlNr9j6-.js → Panel-BV18WuL1.js} +1 -0
- package/public/assets/Rack-D3iFlR6e.js +482 -0
- package/public/assets/{Readout-D8Ofl3Gz.js → Readout-Dxwka1JI.js} +1 -1
- package/public/assets/RequestTable-I395emqH.js +11 -0
- package/public/assets/SummaryDeck-pAZ93jBW.js +15 -0
- package/public/assets/{Toggle-ChjN-tw1.js → Toggle-y17mtXCV.js} +1 -1
- package/public/assets/{TokenBreakdown-BPoXM3JM.js → TokenBreakdown-BIwuEVs8.js} +2 -1
- package/public/assets/{WindowChart-BPEsEGFL.js → WindowChart-DWZtz4Ps.js} +2 -2
- package/public/assets/{_app-CWXzHdhc.js → _app-0fHLZIC7.js} +1 -1
- package/public/assets/_app.accounts-DBlImnPw.js +59 -0
- package/public/assets/{_app.console-Bkdallia.js → _app.console-in_iVnep.js} +2 -2
- package/public/assets/{_app.database-DxqN1395.js → _app.database-CPj-Juag.js} +4 -4
- package/public/assets/_app.index-DGz9njXx.js +67 -0
- package/public/assets/_app.keys-C-Fk39-U.js +92 -0
- package/public/assets/{_app.logs-DGQD8ZAq.js → _app.logs-C7BhBRjg.js} +3 -3
- package/public/assets/{_app.models-D6SV_GVk.js → _app.models-BuiRrOrz.js} +4 -4
- package/public/assets/{_app.plugins._pluginId-CJ7ObXXe.js → _app.plugins._pluginId-Bx1LgcDj.js} +1 -1
- package/public/assets/_app.settings-s6K98CA2.js +88 -0
- package/public/assets/_app.usage-Bp45JtBe.js +76 -0
- package/public/assets/{chevron-right-BfnK63GB.js → chevron-right-BE-3EQR5.js} +1 -1
- package/public/assets/client-Bl_FA5jI.js +39 -0
- package/public/assets/index-nvJxJ9Gp.js +13 -0
- package/public/assets/{login-CrJK9wRA.js → login-Qc9iIepG.js} +2 -2
- package/public/assets/plus-DhyoE7aH.js +1 -0
- package/public/assets/{preload-helper-BknjH67x.js → preload-helper-DYShfVEU.js} +1 -1
- package/public/assets/{shared-oTMOLWMT.js → shared-ChaZCaBz.js} +12 -12
- package/public/assets/{trash-2-CSkf6ME_.js → trash-2-zGGJ7VE_.js} +1 -1
- package/public/index.html +5 -5
- package/public/assets/Rack-_A_3zqxC.js +0 -476
- package/public/assets/RequestTable-Cokk7wKP.js +0 -11
- package/public/assets/SummaryDeck-C0IbqnLz.js +0 -15
- package/public/assets/_app.accounts-DKmxz7Tl.js +0 -59
- package/public/assets/_app.index-BUqor-qH.js +0 -62
- package/public/assets/_app.keys-DMCPEj1g.js +0 -82
- package/public/assets/_app.settings-BVimpngR.js +0 -88
- package/public/assets/_app.usage-Y2Nkaw0t.js +0 -76
- package/public/assets/client-WgQ5A2PD.js +0 -39
- package/public/assets/index-BTE8vyBz.js +0 -13
- package/public/assets/plus-Cv51cK5k.js +0 -1
package/gateway.js
CHANGED
|
@@ -24927,14 +24927,14 @@ function estimateCachedInputTokens(request) {
|
|
|
24927
24927
|
return cached2;
|
|
24928
24928
|
}
|
|
24929
24929
|
// packages/ir/src/validate.ts
|
|
24930
|
-
function validateRequest(req) {
|
|
24930
|
+
function validateRequest(req, newToolUseId = () => `tu_${crypto.randomUUID()}`) {
|
|
24931
24931
|
const seenToolUseIds = new Set;
|
|
24932
24932
|
const cleaned = [];
|
|
24933
24933
|
for (const message of req.messages) {
|
|
24934
24934
|
const content = [];
|
|
24935
24935
|
for (const block of message.content) {
|
|
24936
24936
|
if (block.type === "toolUse") {
|
|
24937
|
-
const id = block.id.length > 0 ? block.id :
|
|
24937
|
+
const id = block.id.length > 0 ? block.id : newToolUseId();
|
|
24938
24938
|
seenToolUseIds.add(id);
|
|
24939
24939
|
content.push({ ...block, id });
|
|
24940
24940
|
continue;
|
|
@@ -25574,13 +25574,34 @@ CREATE TRIGGER credential_health_config_version_del
|
|
|
25574
25574
|
FOR EACH STATEMENT EXECUTE FUNCTION bump_config_version();
|
|
25575
25575
|
`;
|
|
25576
25576
|
|
|
25577
|
+
// packages/store/src/postgres/migrations/003_key_expiry.sql
|
|
25578
|
+
var _003_key_expiry_default = `-- The Postgres half of the sqlite migration of the same name. \`BIGINT\`, like
|
|
25579
|
+
-- every other epoch-ms column in this schema, and read back through \`numOrNull\`.
|
|
25580
|
+
--
|
|
25581
|
+
-- Nullable rather than \`NOT NULL DEFAULT\`: the column is a deadline, and every
|
|
25582
|
+
-- finite default is a date on which somebody's client stops working. NULL is
|
|
25583
|
+
-- the only value that says nothing was decided, and every reader maps it to
|
|
25584
|
+
-- "never expires".
|
|
25585
|
+
--
|
|
25586
|
+
-- No backfill, deliberately. Every key on every existing installation was
|
|
25587
|
+
-- minted on the promise that it works until revoked, so adding the column and
|
|
25588
|
+
-- stopping is what keeps that promise. The absence of an \`UPDATE\` here is the
|
|
25589
|
+
-- substance of the migration, not an omission.
|
|
25590
|
+
--
|
|
25591
|
+
-- Safe to apply under a rolling deploy, unlike migration 2: this adds a
|
|
25592
|
+
-- nullable column and drops nothing, so a replica running the previous image
|
|
25593
|
+
-- keeps serving \u2014 \`SELECT *\` gains a field its mapper ignores.
|
|
25594
|
+
ALTER TABLE api_keys ADD COLUMN expires_at BIGINT;
|
|
25595
|
+
`;
|
|
25596
|
+
|
|
25577
25597
|
// packages/store/src/postgres/db.ts
|
|
25578
25598
|
var num = (v) => Number(v);
|
|
25579
25599
|
var numOrNull = (v) => v === null ? null : Number(v);
|
|
25580
25600
|
var MIGRATION_LOCK = 7140641;
|
|
25581
25601
|
var MIGRATIONS = [
|
|
25582
25602
|
{ id: 1, sql: _001_init_default },
|
|
25583
|
-
{ id: 2, sql: _002_health_measurements_default }
|
|
25603
|
+
{ id: 2, sql: _002_health_measurements_default },
|
|
25604
|
+
{ id: 3, sql: _003_key_expiry_default }
|
|
25584
25605
|
];
|
|
25585
25606
|
async function openPg(url2) {
|
|
25586
25607
|
const sql = new SQL({ url: url2 });
|
|
@@ -25766,6 +25787,9 @@ function resolvePin(target, accounts) {
|
|
|
25766
25787
|
return;
|
|
25767
25788
|
return accounts.find((account) => servesTarget(target, account));
|
|
25768
25789
|
}
|
|
25790
|
+
var keyUsable = (key, now) => key.revokedAt === null && (key.expiresAt === null || key.expiresAt > now);
|
|
25791
|
+
var credentialPastExpiry = (credential, now) => credential.authType === "oauth" && credential.expiresAt !== null && credential.expiresAt <= now;
|
|
25792
|
+
var credentialExpired = (credential, now) => credentialPastExpiry(credential, now) && !credential.hasRefreshToken;
|
|
25769
25793
|
var NODE_GRACE_MS = 60000;
|
|
25770
25794
|
var DEFAULT_SETTINGS = {
|
|
25771
25795
|
weights: { tier: 10, health: 3, quota: 2, load: 2, cost: 1, latency: 1 },
|
|
@@ -26245,7 +26269,8 @@ var toKey = (r, logger2) => ({
|
|
|
26245
26269
|
limits: parseLimits(r.id, r.limits, logger2),
|
|
26246
26270
|
bodyLoggingOptOut: r.body_logging_opt_out,
|
|
26247
26271
|
createdAt: num(r.created_at),
|
|
26248
|
-
revokedAt: numOrNull(r.revoked_at)
|
|
26272
|
+
revokedAt: numOrNull(r.revoked_at),
|
|
26273
|
+
expiresAt: numOrNull(r.expires_at)
|
|
26249
26274
|
});
|
|
26250
26275
|
function createKeyRepo(sql, logger2) {
|
|
26251
26276
|
const one = async (where, arg) => {
|
|
@@ -26263,8 +26288,8 @@ function createKeyRepo(sql, logger2) {
|
|
|
26263
26288
|
async create(input2) {
|
|
26264
26289
|
const now = Date.now();
|
|
26265
26290
|
await sql.unsafe(`INSERT INTO api_keys (id, label, prefix, hash, model_allowlist, limits,
|
|
26266
|
-
body_logging_opt_out, created_at, revoked_at)
|
|
26267
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NULL)`, [
|
|
26291
|
+
body_logging_opt_out, created_at, revoked_at, expires_at)
|
|
26292
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NULL,$9)`, [
|
|
26268
26293
|
input2.id,
|
|
26269
26294
|
input2.label,
|
|
26270
26295
|
input2.prefix,
|
|
@@ -26272,7 +26297,8 @@ function createKeyRepo(sql, logger2) {
|
|
|
26272
26297
|
input2.modelAllowlist === null ? null : JSON.stringify(input2.modelAllowlist),
|
|
26273
26298
|
JSON.stringify(parseLimitConfig(input2.limits)),
|
|
26274
26299
|
input2.bodyLoggingOptOut,
|
|
26275
|
-
now
|
|
26300
|
+
now,
|
|
26301
|
+
input2.expiresAt
|
|
26276
26302
|
]);
|
|
26277
26303
|
return { ...input2, createdAt: now, revokedAt: null };
|
|
26278
26304
|
},
|
|
@@ -26280,8 +26306,8 @@ function createKeyRepo(sql, logger2) {
|
|
|
26280
26306
|
if (row.limits === null)
|
|
26281
26307
|
throw new Error(`api key ${row.id} has unreadable limits`);
|
|
26282
26308
|
await sql.unsafe(`INSERT INTO api_keys (id, label, prefix, hash, model_allowlist, limits,
|
|
26283
|
-
body_logging_opt_out, created_at, revoked_at)
|
|
26284
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`, [
|
|
26309
|
+
body_logging_opt_out, created_at, revoked_at, expires_at)
|
|
26310
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)`, [
|
|
26285
26311
|
row.id,
|
|
26286
26312
|
row.label,
|
|
26287
26313
|
row.prefix,
|
|
@@ -26290,7 +26316,8 @@ function createKeyRepo(sql, logger2) {
|
|
|
26290
26316
|
JSON.stringify(parseLimitConfig(row.limits)),
|
|
26291
26317
|
row.bodyLoggingOptOut,
|
|
26292
26318
|
row.createdAt,
|
|
26293
|
-
row.revokedAt
|
|
26319
|
+
row.revokedAt,
|
|
26320
|
+
row.expiresAt
|
|
26294
26321
|
]);
|
|
26295
26322
|
},
|
|
26296
26323
|
async setLimits(id, limits) {
|
|
@@ -26305,6 +26332,9 @@ function createKeyRepo(sql, logger2) {
|
|
|
26305
26332
|
id
|
|
26306
26333
|
]);
|
|
26307
26334
|
},
|
|
26335
|
+
async setExpiry(id, expiresAt) {
|
|
26336
|
+
await sql.unsafe("UPDATE api_keys SET expires_at = $1 WHERE id = $2", [expiresAt, id]);
|
|
26337
|
+
},
|
|
26308
26338
|
async revoke(id) {
|
|
26309
26339
|
await sql.unsafe("UPDATE api_keys SET revoked_at = $1 WHERE id = $2", [Date.now(), id]);
|
|
26310
26340
|
}
|
|
@@ -28109,6 +28139,35 @@ ALTER TABLE credential_health DROP COLUMN ewma_ttft_ms;
|
|
|
28109
28139
|
ALTER TABLE credential_health DROP COLUMN last_used_at;
|
|
28110
28140
|
`;
|
|
28111
28141
|
|
|
28142
|
+
// packages/store/src/sqlite/migrations/014_key_expiry.sql
|
|
28143
|
+
var _014_key_expiry_default = `-- An optional expiry on a gateway API key: an absolute epoch-ms instant after
|
|
28144
|
+
-- which the key is refused, spelled exactly as \`credentials.expires_at\` is.
|
|
28145
|
+
--
|
|
28146
|
+
-- Nullable rather than \`NOT NULL DEFAULT\`, which is the opposite of the choice
|
|
28147
|
+
-- migration 9 made for \`limits\`, and for the opposite reason. There, every
|
|
28148
|
+
-- possible value is a policy and \`{}\` is the one that means "no ceiling", so a
|
|
28149
|
+
-- default gives "unlimited" a single spelling. Here the column is a deadline,
|
|
28150
|
+
-- and no number means "no deadline" \u2014 every finite default is a date on which
|
|
28151
|
+
-- somebody's client stops working. NULL is the only value that says nothing was
|
|
28152
|
+
-- decided, so it is the column's own default and every reader maps it to
|
|
28153
|
+
-- "never expires".
|
|
28154
|
+
--
|
|
28155
|
+
-- No backfill, deliberately, and this is the whole substance of the migration.
|
|
28156
|
+
-- Every key on every existing installation was minted on the promise that it
|
|
28157
|
+
-- works until revoked; an upgrade that attaches an expiry to those rows breaks
|
|
28158
|
+
-- clients on a date the operator never chose, and the failure surfaces days
|
|
28159
|
+
-- later as an authentication error with nothing in it that names this change.
|
|
28160
|
+
-- Adding the column and stopping is what keeps that promise, so the absence of
|
|
28161
|
+
-- an \`UPDATE\` here is load-bearing rather than an omission \u2014
|
|
28162
|
+
-- \`packages/store/test/migrations.test.ts\` pins it.
|
|
28163
|
+
--
|
|
28164
|
+
-- No index. The column is read only after a row has already been found by
|
|
28165
|
+
-- \`hash\` or by \`id\`, both of which are unique, so there is no scan for an index
|
|
28166
|
+
-- to help; and nothing sweeps expired keys, because an expired key stays listed
|
|
28167
|
+
-- exactly as a revoked one does so its usage history keeps its attribution.
|
|
28168
|
+
ALTER TABLE api_keys ADD COLUMN expires_at INTEGER;
|
|
28169
|
+
`;
|
|
28170
|
+
|
|
28112
28171
|
// packages/store/src/sqlite/db.ts
|
|
28113
28172
|
var MIGRATIONS2 = [
|
|
28114
28173
|
{ id: 1, sql: _001_init_default2 },
|
|
@@ -28123,7 +28182,8 @@ var MIGRATIONS2 = [
|
|
|
28123
28182
|
{ id: 10, sql: _010_usage_rollup_default, after: rebuildRollup },
|
|
28124
28183
|
{ id: 11, sql: _011_plugin_migrations_default },
|
|
28125
28184
|
{ id: 12, sql: _012_nodes_default },
|
|
28126
|
-
{ id: 13, sql: _013_health_measurements_default }
|
|
28185
|
+
{ id: 13, sql: _013_health_measurements_default },
|
|
28186
|
+
{ id: 14, sql: _014_key_expiry_default }
|
|
28127
28187
|
];
|
|
28128
28188
|
function statements(sql) {
|
|
28129
28189
|
return sql.split(`
|
|
@@ -28179,7 +28239,8 @@ var toKey2 = (r, logger2) => ({
|
|
|
28179
28239
|
limits: parseLimits2(r.id, r.limits, logger2),
|
|
28180
28240
|
bodyLoggingOptOut: r.body_logging_opt_out === 1,
|
|
28181
28241
|
createdAt: r.created_at,
|
|
28182
|
-
revokedAt: r.revoked_at
|
|
28242
|
+
revokedAt: r.revoked_at,
|
|
28243
|
+
expiresAt: r.expires_at
|
|
28183
28244
|
});
|
|
28184
28245
|
function createKeyRepo2(db, logger2 = noopLogger) {
|
|
28185
28246
|
return {
|
|
@@ -28197,8 +28258,8 @@ function createKeyRepo2(db, logger2 = noopLogger) {
|
|
|
28197
28258
|
async create(input2) {
|
|
28198
28259
|
const now = Date.now();
|
|
28199
28260
|
db.run(`INSERT INTO api_keys (id, label, prefix, hash, model_allowlist, limits,
|
|
28200
|
-
body_logging_opt_out, created_at, revoked_at)
|
|
28201
|
-
VALUES (?,?,?,?,?,?,?,?,NULL)`, [
|
|
28261
|
+
body_logging_opt_out, created_at, revoked_at, expires_at)
|
|
28262
|
+
VALUES (?,?,?,?,?,?,?,?,NULL,?)`, [
|
|
28202
28263
|
input2.id,
|
|
28203
28264
|
input2.label,
|
|
28204
28265
|
input2.prefix,
|
|
@@ -28206,7 +28267,8 @@ function createKeyRepo2(db, logger2 = noopLogger) {
|
|
|
28206
28267
|
input2.modelAllowlist === null ? null : JSON.stringify(input2.modelAllowlist),
|
|
28207
28268
|
JSON.stringify(parseLimitConfig(input2.limits)),
|
|
28208
28269
|
input2.bodyLoggingOptOut ? 1 : 0,
|
|
28209
|
-
now
|
|
28270
|
+
now,
|
|
28271
|
+
input2.expiresAt
|
|
28210
28272
|
]);
|
|
28211
28273
|
return { ...input2, createdAt: now, revokedAt: null };
|
|
28212
28274
|
},
|
|
@@ -28214,8 +28276,8 @@ function createKeyRepo2(db, logger2 = noopLogger) {
|
|
|
28214
28276
|
if (row.limits === null)
|
|
28215
28277
|
throw new Error(`api key ${row.id} has unreadable limits`);
|
|
28216
28278
|
db.run(`INSERT INTO api_keys (id, label, prefix, hash, model_allowlist, limits,
|
|
28217
|
-
body_logging_opt_out, created_at, revoked_at)
|
|
28218
|
-
VALUES (
|
|
28279
|
+
body_logging_opt_out, created_at, revoked_at, expires_at)
|
|
28280
|
+
VALUES (?,?,?,?,?,?,?,?,?,?)`, [
|
|
28219
28281
|
row.id,
|
|
28220
28282
|
row.label,
|
|
28221
28283
|
row.prefix,
|
|
@@ -28224,7 +28286,8 @@ function createKeyRepo2(db, logger2 = noopLogger) {
|
|
|
28224
28286
|
JSON.stringify(parseLimitConfig(row.limits)),
|
|
28225
28287
|
row.bodyLoggingOptOut ? 1 : 0,
|
|
28226
28288
|
row.createdAt,
|
|
28227
|
-
row.revokedAt
|
|
28289
|
+
row.revokedAt,
|
|
28290
|
+
row.expiresAt
|
|
28228
28291
|
]);
|
|
28229
28292
|
},
|
|
28230
28293
|
async setLimits(id, limits) {
|
|
@@ -28239,6 +28302,9 @@ function createKeyRepo2(db, logger2 = noopLogger) {
|
|
|
28239
28302
|
id
|
|
28240
28303
|
]);
|
|
28241
28304
|
},
|
|
28305
|
+
async setExpiry(id, expiresAt) {
|
|
28306
|
+
db.run("UPDATE api_keys SET expires_at = ? WHERE id = ?", [expiresAt, id]);
|
|
28307
|
+
},
|
|
28242
28308
|
async revoke(id) {
|
|
28243
28309
|
db.run("UPDATE api_keys SET revoked_at = ? WHERE id = ?", [Date.now(), id]);
|
|
28244
28310
|
}
|
|
@@ -28803,6 +28869,7 @@ async function createStore(opts) {
|
|
|
28803
28869
|
importRow: (row) => handle.keys.importRow(row),
|
|
28804
28870
|
setLimits: (id, limits) => handle.keys.setLimits(id, limits),
|
|
28805
28871
|
setModelAllowlist: (id, modelAllowlist) => handle.keys.setModelAllowlist(id, modelAllowlist),
|
|
28872
|
+
setExpiry: (id, expiresAt) => handle.keys.setExpiry(id, expiresAt),
|
|
28806
28873
|
revoke: (id) => handle.keys.revoke(id)
|
|
28807
28874
|
},
|
|
28808
28875
|
usage: {
|
|
@@ -28911,7 +28978,7 @@ function createAdminAuth(store, opts) {
|
|
|
28911
28978
|
};
|
|
28912
28979
|
const keyStillValid = async (apiKeyId) => {
|
|
28913
28980
|
const key = await store.keys.get(apiKeyId);
|
|
28914
|
-
return key !== null && key.
|
|
28981
|
+
return key !== null && keyUsable(key, opts.now());
|
|
28915
28982
|
};
|
|
28916
28983
|
const applyPassword = async (password) => {
|
|
28917
28984
|
if (password.length < MIN_PASSWORD_LENGTH) {
|
|
@@ -28965,7 +29032,7 @@ function createAdminAuth(store, opts) {
|
|
|
28965
29032
|
},
|
|
28966
29033
|
async loginClient(rawKey) {
|
|
28967
29034
|
const key = await store.keys.findByHash(await hashApiKey(rawKey));
|
|
28968
|
-
if (key === null || key.
|
|
29035
|
+
if (key === null || !keyUsable(key, opts.now()))
|
|
28969
29036
|
return null;
|
|
28970
29037
|
return issue2({ kind: "client", apiKeyId: key.id });
|
|
28971
29038
|
},
|
|
@@ -36571,16 +36638,19 @@ var modelSchema = exports_external.object({
|
|
|
36571
36638
|
isAlias: exports_external.boolean(),
|
|
36572
36639
|
targets: exports_external.array(targetSchema).min(1, "a virtual model needs at least one target")
|
|
36573
36640
|
});
|
|
36641
|
+
var MAX_INSTANT = 8640000000000000;
|
|
36574
36642
|
var keyCreateSchema = exports_external.object({
|
|
36575
36643
|
label: exports_external.string().min(1).default("api key"),
|
|
36576
36644
|
modelAllowlist: exports_external.array(exports_external.string().min(1)).nullable().default(null),
|
|
36577
36645
|
limits: limitConfigSchema.default({}),
|
|
36578
|
-
bodyLoggingOptOut: exports_external.boolean().default(false)
|
|
36646
|
+
bodyLoggingOptOut: exports_external.boolean().default(false),
|
|
36647
|
+
expiresAt: exports_external.number().int().positive().max(MAX_INSTANT).nullable().default(null)
|
|
36579
36648
|
}).strict();
|
|
36580
36649
|
var keyLimitsSchema = exports_external.object({ limits: limitConfigSchema }).strict();
|
|
36581
36650
|
var keyModelsSchema = exports_external.object({
|
|
36582
36651
|
modelAllowlist: exports_external.array(exports_external.string().min(1)).nullable()
|
|
36583
36652
|
}).strict();
|
|
36653
|
+
var keyExpirySchema = exports_external.object({ expiresAt: exports_external.number().int().positive().max(MAX_INSTANT).nullable() }).strict();
|
|
36584
36654
|
var retentionSchema = exports_external.object({
|
|
36585
36655
|
keepLatest: exports_external.number().int().min(1).max(100),
|
|
36586
36656
|
maxAgeDays: exports_external.number().int().min(1).max(3650)
|
|
@@ -36795,7 +36865,7 @@ function eligible(input2) {
|
|
|
36795
36865
|
drop("disabled");
|
|
36796
36866
|
continue;
|
|
36797
36867
|
}
|
|
36798
|
-
if (credential
|
|
36868
|
+
if (credentialExpired(credential, now)) {
|
|
36799
36869
|
drop("expired");
|
|
36800
36870
|
continue;
|
|
36801
36871
|
}
|
|
@@ -37741,7 +37811,8 @@ async function toSummary(store, key, now) {
|
|
|
37741
37811
|
limitUsage: await readLimitUsage(store, key.id, key.limits, now),
|
|
37742
37812
|
bodyLoggingOptOut: key.bodyLoggingOptOut,
|
|
37743
37813
|
createdAt: key.createdAt,
|
|
37744
|
-
revokedAt: key.revokedAt
|
|
37814
|
+
revokedAt: key.revokedAt,
|
|
37815
|
+
expiresAt: key.expiresAt
|
|
37745
37816
|
};
|
|
37746
37817
|
}
|
|
37747
37818
|
async function listKeys(store, now = Date.now()) {
|
|
@@ -37764,7 +37835,8 @@ async function createKey(store, input2) {
|
|
|
37764
37835
|
hash: await hashApiKey(raw),
|
|
37765
37836
|
modelAllowlist: body2.modelAllowlist,
|
|
37766
37837
|
limits: body2.limits,
|
|
37767
|
-
bodyLoggingOptOut: body2.bodyLoggingOptOut
|
|
37838
|
+
bodyLoggingOptOut: body2.bodyLoggingOptOut,
|
|
37839
|
+
expiresAt: body2.expiresAt
|
|
37768
37840
|
});
|
|
37769
37841
|
return { id: created.id, label: created.label, prefix: created.prefix, key: raw };
|
|
37770
37842
|
}
|
|
@@ -37784,6 +37856,14 @@ async function setKeyModels(store, id, input2, now = Date.now()) {
|
|
|
37784
37856
|
await store.keys.setModelAllowlist(id, body2.modelAllowlist);
|
|
37785
37857
|
return toSummary(store, { ...key, modelAllowlist: body2.modelAllowlist }, now);
|
|
37786
37858
|
}
|
|
37859
|
+
async function setKeyExpiry(store, id, input2, now = Date.now()) {
|
|
37860
|
+
const body2 = parseOrThrow(keyExpirySchema, input2);
|
|
37861
|
+
const key = await store.keys.get(id);
|
|
37862
|
+
if (key === null)
|
|
37863
|
+
throw new GatewayError("BAD_REQUEST", "no such api key");
|
|
37864
|
+
await store.keys.setExpiry(id, body2.expiresAt);
|
|
37865
|
+
return toSummary(store, { ...key, expiresAt: body2.expiresAt }, now);
|
|
37866
|
+
}
|
|
37787
37867
|
async function revokeKey(store, id) {
|
|
37788
37868
|
await store.keys.revoke(id);
|
|
37789
37869
|
}
|
|
@@ -38634,6 +38714,12 @@ async function accountQuota(deps) {
|
|
|
38634
38714
|
// packages/control/src/quota/history.ts
|
|
38635
38715
|
var DAY_MS3 = 24 * 60 * 60 * 1000;
|
|
38636
38716
|
var HOUR_MS3 = 3600000;
|
|
38717
|
+
var MAX_SAMPLES = 50000;
|
|
38718
|
+
var SAMPLE_QUERY_LIMIT = MAX_SAMPLES + 1;
|
|
38719
|
+
function pageSamples(samples) {
|
|
38720
|
+
const truncated = samples.length > MAX_SAMPLES;
|
|
38721
|
+
return { page: truncated ? samples.slice(0, MAX_SAMPLES) : samples, truncated };
|
|
38722
|
+
}
|
|
38637
38723
|
async function retainedSpan(deps, input2, maxSpanMs) {
|
|
38638
38724
|
const now = deps.now();
|
|
38639
38725
|
const settings = await deps.store.config.getSettings();
|
|
@@ -38680,26 +38766,26 @@ async function quotaHistory(deps, input2) {
|
|
|
38680
38766
|
deps.store.credentials.listQuotaSamples({
|
|
38681
38767
|
since,
|
|
38682
38768
|
until,
|
|
38769
|
+
limit: SAMPLE_QUERY_LIMIT,
|
|
38683
38770
|
...credentialId === undefined ? {} : { credentialId }
|
|
38684
38771
|
}),
|
|
38685
38772
|
deps.store.credentials.listQuota()
|
|
38686
38773
|
]);
|
|
38687
38774
|
const scoped = credentialId === undefined ? quota : quota.filter((w) => w.credentialId === credentialId);
|
|
38688
|
-
|
|
38775
|
+
const { page, truncated } = pageSamples(samples);
|
|
38776
|
+
return { samples: page, truncated, gatewayRates: await gatewayRatesFor(deps.store, scoped) };
|
|
38689
38777
|
}
|
|
38690
38778
|
|
|
38691
38779
|
// packages/control/src/quota/clientHistory.ts
|
|
38692
38780
|
var MAX_SPAN_MS = 16 * 24 * 60 * 60 * 1000;
|
|
38693
|
-
var MAX_SAMPLES = 50000;
|
|
38694
38781
|
async function accountQuotaHistory(deps, input2) {
|
|
38695
38782
|
const { since, until } = await retainedSpan(deps, input2, MAX_SPAN_MS);
|
|
38696
38783
|
const [samples, credentials] = await Promise.all([
|
|
38697
|
-
deps.store.credentials.listQuotaSamples({ since, until, limit:
|
|
38784
|
+
deps.store.credentials.listQuotaSamples({ since, until, limit: SAMPLE_QUERY_LIMIT }),
|
|
38698
38785
|
deps.store.credentials.list()
|
|
38699
38786
|
]);
|
|
38700
38787
|
const byId = new Map(credentials.map((credential) => [credential.id, credential]));
|
|
38701
|
-
const truncated = samples
|
|
38702
|
-
const page = truncated ? samples.slice(0, MAX_SAMPLES) : samples;
|
|
38788
|
+
const { page, truncated } = pageSamples(samples);
|
|
38703
38789
|
const out = [];
|
|
38704
38790
|
for (const sample of page) {
|
|
38705
38791
|
const credential = byId.get(sample.credentialId);
|
|
@@ -55121,6 +55207,11 @@ function adminRoutes(deps) {
|
|
|
55121
55207
|
const key = await setKeyModels(deps.store, params.id, await readJson(request2));
|
|
55122
55208
|
changed("res:keys");
|
|
55123
55209
|
return key;
|
|
55210
|
+
}).put("/api/keys/:id/expiry", async ({ request: request2, params }) => {
|
|
55211
|
+
await requireAdmin(request2, deps.admin);
|
|
55212
|
+
const key = await setKeyExpiry(deps.store, params.id, await readJson(request2));
|
|
55213
|
+
changed("res:keys");
|
|
55214
|
+
return key;
|
|
55124
55215
|
}).delete("/api/keys/:id", async ({ request: request2, params }) => {
|
|
55125
55216
|
await requireAdmin(request2, deps.admin);
|
|
55126
55217
|
await revokeKey(deps.store, params.id);
|
|
@@ -55131,6 +55222,7 @@ function adminRoutes(deps) {
|
|
|
55131
55222
|
return {
|
|
55132
55223
|
settings: await getSettings(deps.store),
|
|
55133
55224
|
bodyLoggingAllowed: deps.bodyLoggingAllowed === true,
|
|
55225
|
+
dayOffsetMinutes: deps.dayOffsetMinutes ?? hostDayOffsetMinutes(),
|
|
55134
55226
|
viewerConfigured: await deps.admin.isViewerConfigured()
|
|
55135
55227
|
};
|
|
55136
55228
|
}).put("/api/settings/password", async ({ request: request2 }) => {
|
|
@@ -55265,7 +55357,10 @@ function clientRoutes(deps) {
|
|
|
55265
55357
|
return { ok: true };
|
|
55266
55358
|
}).get("/api/client/summary", async ({ request: request2 }) => {
|
|
55267
55359
|
const apiKeyId = await requireClient(request2, deps.admin);
|
|
55268
|
-
return
|
|
55360
|
+
return {
|
|
55361
|
+
...await readOwnKey(deps.store, apiKeyId, now()),
|
|
55362
|
+
dayOffsetMinutes: deps.dayOffsetMinutes ?? hostDayOffsetMinutes()
|
|
55363
|
+
};
|
|
55269
55364
|
}).get("/api/client/usage", async ({ request: request2, query }) => {
|
|
55270
55365
|
const apiKeyId = await requireClient(request2, deps.admin);
|
|
55271
55366
|
return queryUsage({ store: deps.store, now }, {
|
|
@@ -55665,12 +55760,12 @@ function injectPonytail(request2, opts) {
|
|
|
55665
55760
|
}
|
|
55666
55761
|
|
|
55667
55762
|
// apps/gateway/src/auth/apiKey.ts
|
|
55668
|
-
async function authenticateApiKey(store, header, logger2 = noopLogger) {
|
|
55763
|
+
async function authenticateApiKey(store, header, now, logger2 = noopLogger) {
|
|
55669
55764
|
const raw = extractToken(header);
|
|
55670
55765
|
if (raw === null)
|
|
55671
55766
|
throw new GatewayError("AUTH", "missing or malformed Authorization header");
|
|
55672
55767
|
const key = await store.keys.findByHash(await hashApiKey(raw));
|
|
55673
|
-
if (key === null || key
|
|
55768
|
+
if (key === null || !keyUsable(key, now)) {
|
|
55674
55769
|
throw new GatewayError("AUTH", "invalid API key");
|
|
55675
55770
|
}
|
|
55676
55771
|
const limits = key.limits;
|
|
@@ -59734,7 +59829,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
59734
59829
|
try {
|
|
59735
59830
|
let key;
|
|
59736
59831
|
try {
|
|
59737
|
-
key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers), deps.logger);
|
|
59832
|
+
key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers), startedAt, deps.logger);
|
|
59738
59833
|
} catch (error61) {
|
|
59739
59834
|
if (error61 instanceof GatewayError && error61.code === "AUTH") {
|
|
59740
59835
|
deps.logger.warn("authentication rejected", {
|
|
@@ -59912,7 +60007,7 @@ function proxyRoutes(deps) {
|
|
|
59912
60007
|
return handle(dispatchDeps, rateLimiter, "openai", request2);
|
|
59913
60008
|
}).get("/v1/models", async ({ request: request2 }) => {
|
|
59914
60009
|
try {
|
|
59915
|
-
const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers), logger2);
|
|
60010
|
+
const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers), deps.now(), logger2);
|
|
59916
60011
|
const snapshot = await dispatchDeps.snapshots.get(deps.now());
|
|
59917
60012
|
const models = [...snapshot.models.values()];
|
|
59918
60013
|
const visibleModels = key.modelAllowlist === null ? models : models.filter((model) => key.modelAllowlist?.includes(model.id));
|
|
@@ -59928,7 +60023,7 @@ function proxyRoutes(deps) {
|
|
|
59928
60023
|
}
|
|
59929
60024
|
}).post("/v1/messages/count_tokens", async ({ request: request2 }) => {
|
|
59930
60025
|
try {
|
|
59931
|
-
const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers), logger2);
|
|
60026
|
+
const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers), deps.now(), logger2);
|
|
59932
60027
|
await rateLimiter.consume(key.id, key.limits, deps.requestId());
|
|
59933
60028
|
const body2 = await request2.json();
|
|
59934
60029
|
const chatRequest = parseAnthropicRequest(body2, request2.headers);
|
|
@@ -60779,7 +60874,8 @@ function sizeOf(payload) {
|
|
|
60779
60874
|
return 0;
|
|
60780
60875
|
}
|
|
60781
60876
|
}
|
|
60782
|
-
|
|
60877
|
+
var DEFAULT_RING_LIMITS = { frames: 500, bytes: 2 * 1024 * 1024 };
|
|
60878
|
+
function createRing(limits = DEFAULT_RING_LIMITS) {
|
|
60783
60879
|
const topics = new Map;
|
|
60784
60880
|
const stateFor = (topic) => {
|
|
60785
60881
|
const existing = topics.get(topic);
|
|
@@ -60842,7 +60938,7 @@ function createRing(limits) {
|
|
|
60842
60938
|
}
|
|
60843
60939
|
|
|
60844
60940
|
// apps/gateway/src/version.ts
|
|
60845
|
-
var VERSION = "0.11.
|
|
60941
|
+
var VERSION = "0.11.2";
|
|
60846
60942
|
|
|
60847
60943
|
// apps/gateway/src/app.ts
|
|
60848
60944
|
var ADMIN_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
|
|
@@ -60929,7 +61025,7 @@ function createApp(deps) {
|
|
|
60929
61025
|
now,
|
|
60930
61026
|
onDetach: (id, topics) => channelsRef?.closed(id, topics)
|
|
60931
61027
|
});
|
|
60932
|
-
const ring = deps.ring ?? createRing(
|
|
61028
|
+
const ring = deps.ring ?? createRing();
|
|
60933
61029
|
const broadcaster = deps.broadcaster ?? createBroadcaster({ registry: registry2, ring, coord, nodeId, now });
|
|
60934
61030
|
const channels = deps.channels ?? createChannelRegistry({
|
|
60935
61031
|
sockets: registry2,
|
|
@@ -60993,6 +61089,7 @@ function createApp(deps) {
|
|
|
60993
61089
|
admin,
|
|
60994
61090
|
baseUrl: deps.baseUrl,
|
|
60995
61091
|
bodyLoggingAllowed: deps.bodyLoggingAllowed === true,
|
|
61092
|
+
...deps.dayOffsetMinutes === undefined ? {} : { dayOffsetMinutes: deps.dayOffsetMinutes },
|
|
60996
61093
|
now,
|
|
60997
61094
|
sessionTtlMs: ADMIN_SESSION_TTL_MS,
|
|
60998
61095
|
logger: logger2,
|
|
@@ -61004,6 +61101,7 @@ function createApp(deps) {
|
|
|
61004
61101
|
store: deps.store,
|
|
61005
61102
|
admin,
|
|
61006
61103
|
sessionTtlMs: ADMIN_SESSION_TTL_MS,
|
|
61104
|
+
...deps.dayOffsetMinutes === undefined ? {} : { dayOffsetMinutes: deps.dayOffsetMinutes },
|
|
61007
61105
|
now,
|
|
61008
61106
|
logger: logger2
|
|
61009
61107
|
})).use(databaseRoutes({
|
|
@@ -61665,7 +61763,7 @@ async function sweep(deps) {
|
|
|
61665
61763
|
let touched = 0;
|
|
61666
61764
|
for (const credential of due(credentials, deps.now())) {
|
|
61667
61765
|
if (!credential.hasRefreshToken) {
|
|
61668
|
-
if (credential
|
|
61766
|
+
if (credentialExpired(credential, deps.now())) {
|
|
61669
61767
|
await deps.store.credentials.update(credential.id, {
|
|
61670
61768
|
enabled: false,
|
|
61671
61769
|
disabledReason: "expiredNoRefresh",
|
|
@@ -62728,7 +62826,7 @@ async function main() {
|
|
|
62728
62826
|
now,
|
|
62729
62827
|
onDetach: (id, topics) => pluginChannelsRef?.closed(id, topics)
|
|
62730
62828
|
});
|
|
62731
|
-
const streamRing = createRing(
|
|
62829
|
+
const streamRing = createRing();
|
|
62732
62830
|
const broadcaster = createBroadcaster({
|
|
62733
62831
|
registry: streamRegistry,
|
|
62734
62832
|
ring: streamRing,
|
|
@@ -62780,6 +62878,7 @@ async function main() {
|
|
|
62780
62878
|
telemetry,
|
|
62781
62879
|
...config2.metricsToken === null ? {} : { metricsToken: config2.metricsToken },
|
|
62782
62880
|
bodyLoggingAllowed: config2.bodyLoggingAllowed,
|
|
62881
|
+
dayOffsetMinutes: config2.dayOffsetMinutes,
|
|
62783
62882
|
plugins: loadedPlugins.plugins.map((plugin) => ({ id: plugin.id, routes: plugin.routes })),
|
|
62784
62883
|
pluginUi: loadedPlugins.plugins,
|
|
62785
62884
|
...loadedPlugins.plugins.length === 0 ? {} : { emit: (event) => pluginEvents.emitRequestCompleted(event) },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnigateway",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Self-hosted AI gateway with Anthropic- and OpenAI-compatible APIs, an admin console, and a CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Harismawan <mail@harismawan.com>",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"LICENSE"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@node-rs/argon2": "2.0
|
|
19
|
+
"@node-rs/argon2": "2.2.0"
|
|
20
20
|
},
|
|
21
21
|
"engines": {
|
|
22
22
|
"bun": ">=1.3.0"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import{m as e}from"./Rack-
|
|
1
|
+
import{m as e}from"./Rack-D3iFlR6e.js";import{jsx as t}from"react/jsx-runtime";import n from"styled-components";var r={neutral:{fg:`var(--ink-dim)`,bg:`transparent`},ok:{fg:`var(--ok)`,bg:`var(--ok-wash)`},warn:{fg:`var(--warn)`,bg:`var(--warn-wash)`},down:{fg:`var(--down)`,bg:`var(--down-wash)`},accent:{fg:`var(--accent)`,bg:`var(--accent-wash)`}},i=n.span`
|
|
2
2
|
display: inline-flex;
|
|
3
3
|
align-items: center;
|
|
4
|
+
line-height: 1;
|
|
4
5
|
gap: 4px;
|
|
5
6
|
height: 18px;
|
|
6
7
|
padding: 0 6px;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{C as e,O as t}from"./Panel-
|
|
1
|
+
import{C as e,O as t}from"./Panel-BV18WuL1.js";import{b as n}from"./Rack-D3iFlR6e.js";import{useCallback as r,useEffect as i,useRef as a,useState as o}from"react";import{jsx as s,jsxs as c}from"react/jsx-runtime";import l from"styled-components";var u=n(`check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),d=n(`copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]);function f(e){if(e===null||typeof getSelection!=`function`)return!1;let t=getSelection();if(t===null)return!1;try{let n=document.createRange();return n.selectNodeContents(e),t.removeAllRanges(),t.addRange(n),!0}catch{return!1}}async function p(e,t){if(typeof navigator<`u`&&navigator.clipboard!==void 0)try{return await navigator.clipboard.writeText(e),`copied`}catch{}let n=f(t??null);if(n&&typeof document.execCommand==`function`)try{if(document.execCommand(`copy`))return`copied`}catch{}return n?`selected`:`failed`}var m=l.div`
|
|
2
2
|
display: flex;
|
|
3
3
|
align-items: stretch;
|
|
4
4
|
gap: 0;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{_ as e}from"./Panel-
|
|
1
|
+
import{_ as e}from"./Panel-BV18WuL1.js";import{useId as t}from"react";import{jsx as n,jsxs as r}from"react/jsx-runtime";import i from"styled-components";var a=`
|
|
2
2
|
width: 100%;
|
|
3
3
|
border-radius: 3px;
|
|
4
4
|
transition: border-color 120ms ease;
|