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/gateway.js
CHANGED
|
@@ -5866,6 +5866,7 @@ async function* decodeAnthropic(messages) {
|
|
|
5866
5866
|
let cacheWriteTokens = 0;
|
|
5867
5867
|
let outputTokens = 0;
|
|
5868
5868
|
let stopReason = "endTurn";
|
|
5869
|
+
let terminal = false;
|
|
5869
5870
|
for await (const msg of messages) {
|
|
5870
5871
|
const d = json(msg.data);
|
|
5871
5872
|
if (d === null)
|
|
@@ -5931,6 +5932,7 @@ async function* decodeAnthropic(messages) {
|
|
|
5931
5932
|
break;
|
|
5932
5933
|
}
|
|
5933
5934
|
case "message_stop":
|
|
5935
|
+
terminal = true;
|
|
5934
5936
|
yield {
|
|
5935
5937
|
type: "end",
|
|
5936
5938
|
stopReason,
|
|
@@ -5938,6 +5940,7 @@ async function* decodeAnthropic(messages) {
|
|
|
5938
5940
|
};
|
|
5939
5941
|
break;
|
|
5940
5942
|
case "error": {
|
|
5943
|
+
terminal = true;
|
|
5941
5944
|
const code = ERROR_TYPE[String(d.error?.type)] ?? "UPSTREAM";
|
|
5942
5945
|
yield {
|
|
5943
5946
|
type: "error",
|
|
@@ -5951,6 +5954,14 @@ async function* decodeAnthropic(messages) {
|
|
|
5951
5954
|
break;
|
|
5952
5955
|
}
|
|
5953
5956
|
}
|
|
5957
|
+
if (!terminal) {
|
|
5958
|
+
yield {
|
|
5959
|
+
type: "error",
|
|
5960
|
+
code: "UPSTREAM",
|
|
5961
|
+
message: "upstream stream ended before message_stop",
|
|
5962
|
+
retryable: RETRYABLE.UPSTREAM
|
|
5963
|
+
};
|
|
5964
|
+
}
|
|
5954
5965
|
}
|
|
5955
5966
|
|
|
5956
5967
|
// packages/providers/src/anthropic/wire.ts
|
|
@@ -6063,9 +6074,10 @@ var anthropicAdapter = {
|
|
|
6063
6074
|
["anthropic-version", API_VERSION],
|
|
6064
6075
|
["Accept", req.request.stream ? "text/event-stream" : "application/json"]
|
|
6065
6076
|
];
|
|
6077
|
+
const betas = new Set(req.request.betas ?? []);
|
|
6066
6078
|
if (oauth) {
|
|
6067
6079
|
protocol.push(["Authorization", `Bearer ${req.credentials.accessToken}`]);
|
|
6068
|
-
|
|
6080
|
+
betas.add(OAUTH_BETA);
|
|
6069
6081
|
} else if (req.credentials.apiKey !== null) {
|
|
6070
6082
|
protocol.push(["x-api-key", req.credentials.apiKey]);
|
|
6071
6083
|
} else {
|
|
@@ -6073,6 +6085,8 @@ var anthropicAdapter = {
|
|
|
6073
6085
|
provider: "anthropic"
|
|
6074
6086
|
});
|
|
6075
6087
|
}
|
|
6088
|
+
if (betas.size > 0)
|
|
6089
|
+
protocol.push(["anthropic-beta", [...betas].join(",")]);
|
|
6076
6090
|
const profile = PROFILES.anthropic;
|
|
6077
6091
|
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
6078
6092
|
const bodyString = signAnthropicBody(JSON.stringify(orderFields(withSystem, BODY_ORDER.anthropic)));
|
|
@@ -6290,18 +6304,16 @@ async function* decodeChat(messages) {
|
|
|
6290
6304
|
let started = false;
|
|
6291
6305
|
let textOpen = false;
|
|
6292
6306
|
let textIndex;
|
|
6293
|
-
let
|
|
6307
|
+
let done = false;
|
|
6294
6308
|
let stopReason = "endTurn";
|
|
6295
6309
|
let usage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
|
|
6296
6310
|
const toolIndex = new Map;
|
|
6297
6311
|
let nextIndex = 0;
|
|
6298
|
-
const emitEnd = () => {
|
|
6299
|
-
ended = true;
|
|
6300
|
-
return { type: "end", stopReason, usage };
|
|
6301
|
-
};
|
|
6302
6312
|
for await (const msg of messages) {
|
|
6303
|
-
if (msg.data === "[DONE]")
|
|
6313
|
+
if (msg.data === "[DONE]") {
|
|
6314
|
+
done = true;
|
|
6304
6315
|
break;
|
|
6316
|
+
}
|
|
6305
6317
|
const d = json2(msg.data);
|
|
6306
6318
|
if (d === null)
|
|
6307
6319
|
continue;
|
|
@@ -6356,19 +6368,21 @@ async function* decodeChat(messages) {
|
|
|
6356
6368
|
}
|
|
6357
6369
|
if (typeof choice.finish_reason === "string") {
|
|
6358
6370
|
stopReason = FINISH[choice.finish_reason] ?? "endTurn";
|
|
6359
|
-
if (textOpen)
|
|
6360
|
-
yield { type: "blockEnd", index: textIndex ?? 0 };
|
|
6361
|
-
for (const index of toolIndex.values())
|
|
6362
|
-
yield { type: "blockEnd", index };
|
|
6363
|
-
yield emitEnd();
|
|
6364
6371
|
}
|
|
6365
6372
|
}
|
|
6366
|
-
if (
|
|
6373
|
+
if (done) {
|
|
6367
6374
|
if (textOpen)
|
|
6368
6375
|
yield { type: "blockEnd", index: textIndex ?? 0 };
|
|
6369
6376
|
for (const index of toolIndex.values())
|
|
6370
6377
|
yield { type: "blockEnd", index };
|
|
6371
|
-
yield
|
|
6378
|
+
yield { type: "end", stopReason, usage };
|
|
6379
|
+
} else {
|
|
6380
|
+
yield {
|
|
6381
|
+
type: "error",
|
|
6382
|
+
code: "UPSTREAM",
|
|
6383
|
+
message: "upstream stream ended before [DONE]",
|
|
6384
|
+
retryable: RETRYABLE.UPSTREAM
|
|
6385
|
+
};
|
|
6372
6386
|
}
|
|
6373
6387
|
}
|
|
6374
6388
|
|
|
@@ -6523,6 +6537,7 @@ async function* decodeResponses(messages) {
|
|
|
6523
6537
|
return assigned;
|
|
6524
6538
|
};
|
|
6525
6539
|
let sawToolCall = false;
|
|
6540
|
+
let terminal = false;
|
|
6526
6541
|
const ownsBlock = new Set;
|
|
6527
6542
|
for await (const msg of messages) {
|
|
6528
6543
|
const d = json3(msg.data);
|
|
@@ -6598,6 +6613,7 @@ async function* decodeResponses(messages) {
|
|
|
6598
6613
|
}
|
|
6599
6614
|
case "response.completed":
|
|
6600
6615
|
case "response.incomplete": {
|
|
6616
|
+
terminal = true;
|
|
6601
6617
|
const r = d.response ?? {};
|
|
6602
6618
|
const reason = r.incomplete_details?.reason;
|
|
6603
6619
|
let stopReason = sawToolCall ? "toolUse" : "endTurn";
|
|
@@ -6619,6 +6635,7 @@ async function* decodeResponses(messages) {
|
|
|
6619
6635
|
}
|
|
6620
6636
|
case "response.failed":
|
|
6621
6637
|
case "error": {
|
|
6638
|
+
terminal = true;
|
|
6622
6639
|
const err = d.response?.error ?? d.error ?? {};
|
|
6623
6640
|
const code = ERROR_CODE[String(err.code ?? err.type)] ?? "UPSTREAM";
|
|
6624
6641
|
yield {
|
|
@@ -6633,6 +6650,14 @@ async function* decodeResponses(messages) {
|
|
|
6633
6650
|
break;
|
|
6634
6651
|
}
|
|
6635
6652
|
}
|
|
6653
|
+
if (!terminal) {
|
|
6654
|
+
yield {
|
|
6655
|
+
type: "error",
|
|
6656
|
+
code: "UPSTREAM",
|
|
6657
|
+
message: "upstream stream ended before response completion",
|
|
6658
|
+
retryable: RETRYABLE.UPSTREAM
|
|
6659
|
+
};
|
|
6660
|
+
}
|
|
6636
6661
|
}
|
|
6637
6662
|
|
|
6638
6663
|
// packages/providers/src/openai/wire.ts
|
|
@@ -21679,25 +21704,27 @@ function requireDimension(grain, dimension) {
|
|
|
21679
21704
|
}
|
|
21680
21705
|
|
|
21681
21706
|
// packages/control/src/credentials.ts
|
|
21707
|
+
function summarizeCredential(credential) {
|
|
21708
|
+
return {
|
|
21709
|
+
id: credential.id,
|
|
21710
|
+
provider: credential.provider,
|
|
21711
|
+
label: credential.label,
|
|
21712
|
+
authType: credential.authType,
|
|
21713
|
+
enabled: credential.enabled,
|
|
21714
|
+
tier: credential.tier,
|
|
21715
|
+
weight: credential.weight,
|
|
21716
|
+
expiresAt: credential.expiresAt,
|
|
21717
|
+
accountEmail: credential.accountEmail,
|
|
21718
|
+
providerData: credential.providerData,
|
|
21719
|
+
disabledReason: credential.disabledReason,
|
|
21720
|
+
disabledAt: credential.disabledAt,
|
|
21721
|
+
hasRefreshToken: credential.hasRefreshToken,
|
|
21722
|
+
createdAt: credential.createdAt,
|
|
21723
|
+
updatedAt: credential.updatedAt
|
|
21724
|
+
};
|
|
21725
|
+
}
|
|
21682
21726
|
async function listCredentials(store) {
|
|
21683
|
-
|
|
21684
|
-
return credentials.map((c) => ({
|
|
21685
|
-
id: c.id,
|
|
21686
|
-
provider: c.provider,
|
|
21687
|
-
label: c.label,
|
|
21688
|
-
authType: c.authType,
|
|
21689
|
-
enabled: c.enabled,
|
|
21690
|
-
tier: c.tier,
|
|
21691
|
-
weight: c.weight,
|
|
21692
|
-
expiresAt: c.expiresAt,
|
|
21693
|
-
accountEmail: c.accountEmail,
|
|
21694
|
-
providerData: c.providerData,
|
|
21695
|
-
disabledReason: c.disabledReason,
|
|
21696
|
-
disabledAt: c.disabledAt,
|
|
21697
|
-
hasRefreshToken: c.hasRefreshToken,
|
|
21698
|
-
createdAt: c.createdAt,
|
|
21699
|
-
updatedAt: c.updatedAt
|
|
21700
|
-
}));
|
|
21727
|
+
return (await store.credentials.list()).map(summarizeCredential);
|
|
21701
21728
|
}
|
|
21702
21729
|
async function credentialHealth(store) {
|
|
21703
21730
|
const [health, quota] = await Promise.all([
|
|
@@ -21732,7 +21759,7 @@ function healthKey(credentialId, model) {
|
|
|
21732
21759
|
}
|
|
21733
21760
|
async function buildSnapshot(store, now) {
|
|
21734
21761
|
const [credentials, healthRows, quotaRows, models, settings] = await Promise.all([
|
|
21735
|
-
store.credentials.
|
|
21762
|
+
store.credentials.listRouting(),
|
|
21736
21763
|
store.credentials.listHealth(),
|
|
21737
21764
|
store.credentials.listQuota(),
|
|
21738
21765
|
store.config.listModels(),
|
|
@@ -22151,7 +22178,7 @@ var DEFAULT_SETTINGS = {
|
|
|
22151
22178
|
// packages/store/src/sqlite/config.ts
|
|
22152
22179
|
var SETTINGS_KEY = "settings";
|
|
22153
22180
|
var ADMIN_HASH_KEY = "adminPasswordHash";
|
|
22154
|
-
function createConfigRepo(db) {
|
|
22181
|
+
function createConfigRepo(db, emit = () => {}) {
|
|
22155
22182
|
const readRaw = (key) => db.query("SELECT value FROM settings WHERE key = ?").get(key)?.value ?? null;
|
|
22156
22183
|
const writeRaw = (key, value) => {
|
|
22157
22184
|
db.run("INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value", [key, value]);
|
|
@@ -22171,9 +22198,11 @@ function createConfigRepo(db) {
|
|
|
22171
22198
|
targets = excluded.targets,
|
|
22172
22199
|
strategy = excluded.strategy,
|
|
22173
22200
|
is_alias = excluded.is_alias`, [model.id, JSON.stringify(model.targets), model.strategy, model.isAlias ? 1 : 0]);
|
|
22201
|
+
emit({ type: "modelsChanged" });
|
|
22174
22202
|
},
|
|
22175
22203
|
async removeModel(id) {
|
|
22176
22204
|
db.run("DELETE FROM virtual_models WHERE id = ?", [id]);
|
|
22205
|
+
emit({ type: "modelsChanged" });
|
|
22177
22206
|
},
|
|
22178
22207
|
async getSettings() {
|
|
22179
22208
|
const raw = readRaw(SETTINGS_KEY);
|
|
@@ -22194,6 +22223,7 @@ function createConfigRepo(db) {
|
|
|
22194
22223
|
weights: { ...current.weights, ...patch.weights }
|
|
22195
22224
|
};
|
|
22196
22225
|
writeRaw(SETTINGS_KEY, JSON.stringify(next));
|
|
22226
|
+
emit({ type: "settingsChanged" });
|
|
22197
22227
|
return next;
|
|
22198
22228
|
},
|
|
22199
22229
|
async getAdminPasswordHash() {
|
|
@@ -22209,8 +22239,40 @@ function createConfigRepo(db) {
|
|
|
22209
22239
|
};
|
|
22210
22240
|
}
|
|
22211
22241
|
// packages/store/src/sqlite/credentials.ts
|
|
22212
|
-
function createCredentialRepo(db, key) {
|
|
22213
|
-
const
|
|
22242
|
+
function createCredentialRepo(db, key, emit = () => {}) {
|
|
22243
|
+
const open = async (v) => v === null ? null : decrypt(key, v);
|
|
22244
|
+
const secretsFrom = async (row) => ({
|
|
22245
|
+
accessToken: await open(row.access_token),
|
|
22246
|
+
refreshToken: await open(row.refresh_token),
|
|
22247
|
+
apiKey: await open(row.api_key),
|
|
22248
|
+
idToken: await open(row.id_token)
|
|
22249
|
+
});
|
|
22250
|
+
const requiredRow = (row, id) => {
|
|
22251
|
+
if (row === null)
|
|
22252
|
+
throw new Error(`credential ${id} no longer exists`);
|
|
22253
|
+
return row;
|
|
22254
|
+
};
|
|
22255
|
+
const currentSecrets = async (id) => {
|
|
22256
|
+
const row = requiredRow(db.query("SELECT access_token, refresh_token, api_key, id_token FROM credentials WHERE id = ?").get(id), id);
|
|
22257
|
+
return secretsFrom(row);
|
|
22258
|
+
};
|
|
22259
|
+
const currentInferenceSecrets = async (id, authType) => {
|
|
22260
|
+
if (authType === "oauth") {
|
|
22261
|
+
const row2 = requiredRow(db.query("SELECT access_token FROM credentials WHERE id = ?").get(id), id);
|
|
22262
|
+
return { accessToken: await open(row2.access_token), apiKey: null };
|
|
22263
|
+
}
|
|
22264
|
+
const row = requiredRow(db.query("SELECT api_key FROM credentials WHERE id = ?").get(id), id);
|
|
22265
|
+
return { accessToken: null, apiKey: await open(row.api_key) };
|
|
22266
|
+
};
|
|
22267
|
+
const currentRefreshSecrets = async (id) => {
|
|
22268
|
+
const row = requiredRow(db.query("SELECT refresh_token FROM credentials WHERE id = ?").get(id), id);
|
|
22269
|
+
return { refreshToken: await open(row.refresh_token) };
|
|
22270
|
+
};
|
|
22271
|
+
const currentUsageSecrets = async (id) => {
|
|
22272
|
+
const row = requiredRow(db.query("SELECT access_token FROM credentials WHERE id = ?").get(id), id);
|
|
22273
|
+
return { accessToken: await open(row.access_token) };
|
|
22274
|
+
};
|
|
22275
|
+
const view = (row, loadCurrentSecrets = false) => ({
|
|
22214
22276
|
id: row.id,
|
|
22215
22277
|
provider: row.provider,
|
|
22216
22278
|
label: row.label,
|
|
@@ -22226,18 +22288,31 @@ function createCredentialRepo(db, key) {
|
|
|
22226
22288
|
hasRefreshToken: row.refresh_token !== null,
|
|
22227
22289
|
createdAt: row.created_at,
|
|
22228
22290
|
updatedAt: row.updated_at,
|
|
22229
|
-
secrets:
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
|
|
22233
|
-
|
|
22234
|
-
|
|
22291
|
+
secrets: () => loadCurrentSecrets ? currentSecrets(row.id) : secretsFrom(row),
|
|
22292
|
+
openForInference: async () => {
|
|
22293
|
+
if (loadCurrentSecrets)
|
|
22294
|
+
return currentInferenceSecrets(row.id, row.auth_type);
|
|
22295
|
+
if (row.auth_type === "oauth") {
|
|
22296
|
+
return { accessToken: await open(row.access_token), apiKey: null };
|
|
22297
|
+
}
|
|
22298
|
+
return { accessToken: null, apiKey: await open(row.api_key) };
|
|
22299
|
+
},
|
|
22300
|
+
openForRefresh: async () => loadCurrentSecrets ? currentRefreshSecrets(row.id) : { refreshToken: await open(row.refresh_token) },
|
|
22301
|
+
openForUsage: async () => loadCurrentSecrets ? currentUsageSecrets(row.id) : { accessToken: await open(row.access_token) }
|
|
22235
22302
|
});
|
|
22236
|
-
const open = async (v) => v === null ? null : decrypt(key, v);
|
|
22237
22303
|
const seal = async (v) => v === null || v === undefined ? null : encrypt(key, v);
|
|
22238
22304
|
return {
|
|
22239
22305
|
async list() {
|
|
22240
|
-
return db.query("SELECT * FROM credentials ORDER BY tier, label").all().map(view);
|
|
22306
|
+
return db.query("SELECT * FROM credentials ORDER BY tier, label").all().map((row) => view(row));
|
|
22307
|
+
},
|
|
22308
|
+
async listRouting() {
|
|
22309
|
+
return db.query(`SELECT id, provider, label, auth_type, enabled, tier, weight, expires_at,
|
|
22310
|
+
account_email, provider_data, disabled_reason, disabled_at,
|
|
22311
|
+
NULL AS access_token,
|
|
22312
|
+
CASE WHEN refresh_token IS NULL THEN NULL ELSE 'present' END AS refresh_token,
|
|
22313
|
+
NULL AS api_key, NULL AS id_token, created_at, updated_at
|
|
22314
|
+
FROM credentials
|
|
22315
|
+
ORDER BY tier, label`).all().map((row) => view(row, true));
|
|
22241
22316
|
},
|
|
22242
22317
|
async get(id) {
|
|
22243
22318
|
const row = db.query("SELECT * FROM credentials WHERE id = ?").get(id);
|
|
@@ -22270,6 +22345,7 @@ function createCredentialRepo(db, key) {
|
|
|
22270
22345
|
now
|
|
22271
22346
|
]);
|
|
22272
22347
|
const { accessToken, refreshToken, apiKey, idToken, ...meta3 } = input;
|
|
22348
|
+
emit({ type: "credentialsChanged" });
|
|
22273
22349
|
return {
|
|
22274
22350
|
...meta3,
|
|
22275
22351
|
hasRefreshToken: refreshToken != null,
|
|
@@ -22306,6 +22382,7 @@ function createCredentialRepo(db, key) {
|
|
|
22306
22382
|
return;
|
|
22307
22383
|
put("updated_at", Date.now());
|
|
22308
22384
|
db.run(`UPDATE credentials SET ${sets.join(", ")} WHERE id = ?`, [...vals, id]);
|
|
22385
|
+
emit({ type: "credentialsChanged" });
|
|
22309
22386
|
},
|
|
22310
22387
|
async updateSecrets(id, secrets, expiresAt) {
|
|
22311
22388
|
const sets = [];
|
|
@@ -22329,9 +22406,11 @@ function createCredentialRepo(db, key) {
|
|
|
22329
22406
|
sets.push("expires_at = ?", "updated_at = ?");
|
|
22330
22407
|
vals.push(expiresAt, Date.now());
|
|
22331
22408
|
db.run(`UPDATE credentials SET ${sets.join(", ")} WHERE id = ?`, [...vals, id]);
|
|
22409
|
+
emit({ type: "credentialsChanged" });
|
|
22332
22410
|
},
|
|
22333
22411
|
async remove(id) {
|
|
22334
22412
|
db.run("DELETE FROM credentials WHERE id = ?", [id]);
|
|
22413
|
+
emit({ type: "credentialsChanged" });
|
|
22335
22414
|
},
|
|
22336
22415
|
async listHealth() {
|
|
22337
22416
|
return db.query("SELECT * FROM credential_health").all().map((r) => ({
|
|
@@ -22362,6 +22441,7 @@ function createCredentialRepo(db, key) {
|
|
|
22362
22441
|
stmt.run(r.credentialId, r.model, r.breakerState, r.consecutiveFailures, r.openedAt, r.rateLimitedUntil, r.ewmaTtftMs, r.lastUsedAt);
|
|
22363
22442
|
}
|
|
22364
22443
|
})();
|
|
22444
|
+
emit({ type: "healthSaved", rows });
|
|
22365
22445
|
},
|
|
22366
22446
|
async listQuota() {
|
|
22367
22447
|
return db.query("SELECT * FROM quota_windows").all().map((r) => ({
|
|
@@ -22401,6 +22481,7 @@ function createCredentialRepo(db, key) {
|
|
|
22401
22481
|
prune.run(credentialId, JSON.stringify(types2));
|
|
22402
22482
|
}
|
|
22403
22483
|
})();
|
|
22484
|
+
emit({ type: "quotaSaved", rows });
|
|
22404
22485
|
}
|
|
22405
22486
|
};
|
|
22406
22487
|
}
|
|
@@ -22873,11 +22954,26 @@ function createUsageRepo(db) {
|
|
|
22873
22954
|
// packages/store/src/sqlite/store.ts
|
|
22874
22955
|
async function createStore(opts) {
|
|
22875
22956
|
const db = openDb(opts.path);
|
|
22957
|
+
const listeners = new Set;
|
|
22958
|
+
const emit = (change) => {
|
|
22959
|
+
for (const listener of listeners) {
|
|
22960
|
+
try {
|
|
22961
|
+
listener(change);
|
|
22962
|
+
} catch {}
|
|
22963
|
+
}
|
|
22964
|
+
};
|
|
22876
22965
|
return {
|
|
22877
|
-
credentials: createCredentialRepo(db, opts.encryptionKey),
|
|
22878
|
-
config: createConfigRepo(db),
|
|
22966
|
+
credentials: createCredentialRepo(db, opts.encryptionKey, emit),
|
|
22967
|
+
config: createConfigRepo(db, emit),
|
|
22879
22968
|
keys: createKeyRepo(db),
|
|
22880
22969
|
usage: createUsageRepo(db),
|
|
22970
|
+
routing: {
|
|
22971
|
+
version: () => db.query("PRAGMA data_version").get()?.data_version ?? 0,
|
|
22972
|
+
subscribe(listener) {
|
|
22973
|
+
listeners.add(listener);
|
|
22974
|
+
return () => listeners.delete(listener);
|
|
22975
|
+
}
|
|
22976
|
+
},
|
|
22881
22977
|
close: () => db.close()
|
|
22882
22978
|
};
|
|
22883
22979
|
}
|
|
@@ -23202,7 +23298,7 @@ var SCHEDULER_REFRESH_LEAD_MS = 300000;
|
|
|
23202
23298
|
function createRefresher(deps) {
|
|
23203
23299
|
const inFlight = new Map;
|
|
23204
23300
|
async function run(credential) {
|
|
23205
|
-
const secrets = await credential.
|
|
23301
|
+
const secrets = await credential.openForRefresh();
|
|
23206
23302
|
if (secrets.refreshToken === null) {
|
|
23207
23303
|
throw new GatewayError("AUTH", `credential ${credential.id} has no refresh token`);
|
|
23208
23304
|
}
|
|
@@ -23248,10 +23344,8 @@ async function probe(deps, credential) {
|
|
|
23248
23344
|
const provider = deps.providers[credential.provider];
|
|
23249
23345
|
if (provider.usage === undefined)
|
|
23250
23346
|
return null;
|
|
23251
|
-
|
|
23252
|
-
|
|
23253
|
-
secrets = await deps.refresh(credential);
|
|
23254
|
-
}
|
|
23347
|
+
const refreshed = credential.hasRefreshToken && credential.expiresAt !== null && credential.expiresAt - SCHEDULER_REFRESH_LEAD_MS <= deps.now() ? await deps.refresh(credential) : null;
|
|
23348
|
+
const secrets = refreshed === null ? await credential.openForUsage() : { accessToken: refreshed.accessToken };
|
|
23255
23349
|
const report = await provider.usage(secrets, { http: deps.http, now: deps.now }, credential.providerData);
|
|
23256
23350
|
if (report === null || report.windows.length === 0)
|
|
23257
23351
|
return null;
|
|
@@ -37744,6 +37838,64 @@ class ApiKeyRateLimiter {
|
|
|
37744
37838
|
}
|
|
37745
37839
|
}
|
|
37746
37840
|
|
|
37841
|
+
// apps/gateway/src/dispatch/snapshotCache.ts
|
|
37842
|
+
function createRoutingSnapshotCache(store) {
|
|
37843
|
+
let snapshot = null;
|
|
37844
|
+
let stale = true;
|
|
37845
|
+
let version2 = store.routing.version();
|
|
37846
|
+
let generation = 0;
|
|
37847
|
+
let inFlight = null;
|
|
37848
|
+
const apply = (change) => {
|
|
37849
|
+
generation++;
|
|
37850
|
+
if (snapshot === null || stale)
|
|
37851
|
+
return;
|
|
37852
|
+
if (change.type === "healthSaved") {
|
|
37853
|
+
const health = new Map(snapshot.health);
|
|
37854
|
+
for (const row of change.rows)
|
|
37855
|
+
health.set(healthKey(row.credentialId, row.model), row);
|
|
37856
|
+
snapshot = { ...snapshot, health };
|
|
37857
|
+
return;
|
|
37858
|
+
}
|
|
37859
|
+
if (change.type === "quotaSaved") {
|
|
37860
|
+
const quota = new Map(snapshot.quota);
|
|
37861
|
+
const credentialIds = new Set(change.rows.map((row) => row.credentialId));
|
|
37862
|
+
for (const credentialId of credentialIds) {
|
|
37863
|
+
quota.set(credentialId, change.rows.filter((row) => row.credentialId === credentialId));
|
|
37864
|
+
}
|
|
37865
|
+
snapshot = { ...snapshot, quota };
|
|
37866
|
+
return;
|
|
37867
|
+
}
|
|
37868
|
+
stale = true;
|
|
37869
|
+
};
|
|
37870
|
+
const unsubscribe = store.routing.subscribe(apply);
|
|
37871
|
+
const get = async (now) => {
|
|
37872
|
+
const currentVersion = store.routing.version();
|
|
37873
|
+
if (currentVersion !== version2)
|
|
37874
|
+
stale = true;
|
|
37875
|
+
if (snapshot !== null && !stale)
|
|
37876
|
+
return snapshot;
|
|
37877
|
+
if (inFlight !== null)
|
|
37878
|
+
return inFlight;
|
|
37879
|
+
const buildGeneration = generation;
|
|
37880
|
+
const buildVersion = currentVersion;
|
|
37881
|
+
inFlight = buildSnapshot(store, now).then(async (next) => {
|
|
37882
|
+
const completedVersion = store.routing.version();
|
|
37883
|
+
if (generation !== buildGeneration || completedVersion !== buildVersion) {
|
|
37884
|
+
inFlight = null;
|
|
37885
|
+
return get(now);
|
|
37886
|
+
}
|
|
37887
|
+
snapshot = next;
|
|
37888
|
+
version2 = completedVersion;
|
|
37889
|
+
stale = false;
|
|
37890
|
+
return next;
|
|
37891
|
+
}).finally(() => {
|
|
37892
|
+
inFlight = null;
|
|
37893
|
+
});
|
|
37894
|
+
return inFlight;
|
|
37895
|
+
};
|
|
37896
|
+
return { get, close: unsubscribe };
|
|
37897
|
+
}
|
|
37898
|
+
|
|
37747
37899
|
// apps/gateway/src/ingress/schemas.ts
|
|
37748
37900
|
function parseOrThrow2(schema, body2) {
|
|
37749
37901
|
const result = schema.safeParse(body2);
|
|
@@ -38021,7 +38173,7 @@ function extractToken(header) {
|
|
|
38021
38173
|
async function attempt(opts) {
|
|
38022
38174
|
const { candidate, adapter, http, now, signal, refresh, refreshLeadMs } = opts;
|
|
38023
38175
|
const credential = candidate.credential;
|
|
38024
|
-
let secrets = opts.secrets ?? await credential.
|
|
38176
|
+
let secrets = opts.secrets ?? await credential.openForInference();
|
|
38025
38177
|
const stale = opts.secrets === undefined && credential.authType === "oauth" && credential.expiresAt !== null && credential.expiresAt - refreshLeadMs <= now;
|
|
38026
38178
|
if (stale) {
|
|
38027
38179
|
if (!credential.hasRefreshToken) {
|
|
@@ -38072,7 +38224,7 @@ function classify(error51) {
|
|
|
38072
38224
|
// apps/gateway/src/dispatch/index.ts
|
|
38073
38225
|
async function dispatch(request2, deps, signal) {
|
|
38074
38226
|
const startedAt = deps.now();
|
|
38075
|
-
const snapshot = await
|
|
38227
|
+
const snapshot = await deps.snapshots.get(startedAt);
|
|
38076
38228
|
const deadlineAt = startedAt + snapshot.settings.requestDeadlineMs;
|
|
38077
38229
|
const log = {
|
|
38078
38230
|
id: crypto.randomUUID(),
|
|
@@ -38192,12 +38344,18 @@ async function dispatch(request2, deps, signal) {
|
|
|
38192
38344
|
}), dispatchSignal);
|
|
38193
38345
|
for (const d of result.degradations)
|
|
38194
38346
|
log.degradations.push(d);
|
|
38347
|
+
let terminal = false;
|
|
38348
|
+
const pending = [];
|
|
38195
38349
|
for await (const event of result.events) {
|
|
38196
38350
|
if (event.type === "blockDelta" && !committed) {
|
|
38197
38351
|
committed = true;
|
|
38198
38352
|
log.ttftMs = deps.now() - startedAt;
|
|
38353
|
+
for (const buffered of pending)
|
|
38354
|
+
yield buffered;
|
|
38355
|
+
pending.length = 0;
|
|
38199
38356
|
}
|
|
38200
38357
|
if (event.type === "end") {
|
|
38358
|
+
terminal = true;
|
|
38201
38359
|
log.inputTokens = event.usage.inputTokens;
|
|
38202
38360
|
log.outputTokens = event.usage.outputTokens;
|
|
38203
38361
|
log.cacheReadTokens = event.usage.cacheReadTokens;
|
|
@@ -38205,6 +38363,7 @@ async function dispatch(request2, deps, signal) {
|
|
|
38205
38363
|
log.costUsd = priceOf(candidate, event.usage);
|
|
38206
38364
|
}
|
|
38207
38365
|
if (event.type === "error") {
|
|
38366
|
+
terminal = true;
|
|
38208
38367
|
if (!committed && RETRYABLE[event.code]) {
|
|
38209
38368
|
throw new GatewayError(event.code, event.message);
|
|
38210
38369
|
}
|
|
@@ -38221,7 +38380,21 @@ async function dispatch(request2, deps, signal) {
|
|
|
38221
38380
|
log.durationMs = deps.now() - startedAt;
|
|
38222
38381
|
return;
|
|
38223
38382
|
}
|
|
38224
|
-
|
|
38383
|
+
if (!committed && event.type !== "end") {
|
|
38384
|
+
pending.push(event);
|
|
38385
|
+
} else {
|
|
38386
|
+
if (!committed) {
|
|
38387
|
+
for (const buffered of pending)
|
|
38388
|
+
yield buffered;
|
|
38389
|
+
pending.length = 0;
|
|
38390
|
+
}
|
|
38391
|
+
yield event;
|
|
38392
|
+
}
|
|
38393
|
+
if (event.type === "end")
|
|
38394
|
+
break;
|
|
38395
|
+
}
|
|
38396
|
+
if (!terminal) {
|
|
38397
|
+
throw new GatewayError("UPSTREAM", "upstream stream ended without a terminal event");
|
|
38225
38398
|
}
|
|
38226
38399
|
await persistHealth(recordSuccess(healthFor(candidate), {
|
|
38227
38400
|
settings: snapshot.settings,
|
|
@@ -38475,6 +38648,7 @@ var chunk = (id, created, model, choice, usage) => ({
|
|
|
38475
38648
|
async function* openaiStream(events, requestId, created) {
|
|
38476
38649
|
let model = "";
|
|
38477
38650
|
let roleSent = false;
|
|
38651
|
+
let completed = false;
|
|
38478
38652
|
const toolIndex = new Map;
|
|
38479
38653
|
const emit = (data) => ({ event: "message", data: JSON.stringify(data) });
|
|
38480
38654
|
for await (const event of events) {
|
|
@@ -38536,6 +38710,7 @@ async function* openaiStream(events, requestId, created) {
|
|
|
38536
38710
|
case "blockEnd":
|
|
38537
38711
|
break;
|
|
38538
38712
|
case "end":
|
|
38713
|
+
completed = true;
|
|
38539
38714
|
yield emit(chunk(requestId, created, model, { index: 0, delta: {}, finish_reason: FINISH2[event.stopReason] }, {
|
|
38540
38715
|
prompt_tokens: event.usage.inputTokens,
|
|
38541
38716
|
completion_tokens: event.usage.outputTokens,
|
|
@@ -38549,7 +38724,8 @@ async function* openaiStream(events, requestId, created) {
|
|
|
38549
38724
|
}
|
|
38550
38725
|
}
|
|
38551
38726
|
}
|
|
38552
|
-
|
|
38727
|
+
if (completed)
|
|
38728
|
+
yield { event: "message", data: "[DONE]" };
|
|
38553
38729
|
}
|
|
38554
38730
|
function openaiResponse(collected, requestId, created) {
|
|
38555
38731
|
const text = collected.content.flatMap((b) => b.type === "text" ? [b.text] : []).join("");
|
|
@@ -38728,7 +38904,14 @@ function toIrReasoning(thinking, effort) {
|
|
|
38728
38904
|
return { mode: "off" };
|
|
38729
38905
|
}
|
|
38730
38906
|
}
|
|
38731
|
-
function
|
|
38907
|
+
function readBetas(headers) {
|
|
38908
|
+
const raw = headers?.get("anthropic-beta");
|
|
38909
|
+
if (raw === undefined || raw === null)
|
|
38910
|
+
return [];
|
|
38911
|
+
const names = raw.split(",").map((name) => name.trim()).filter((name) => name.length > 0);
|
|
38912
|
+
return [...new Set(names)];
|
|
38913
|
+
}
|
|
38914
|
+
function parseAnthropicRequest(body2, headers) {
|
|
38732
38915
|
if (typeof body2 !== "object" || body2 === null) {
|
|
38733
38916
|
throw new GatewayError("BAD_REQUEST", "request body must be a JSON object");
|
|
38734
38917
|
}
|
|
@@ -38769,6 +38952,9 @@ function parseAnthropicRequest(body2) {
|
|
|
38769
38952
|
const extras = extraFields(body2, KNOWN);
|
|
38770
38953
|
if (extras !== undefined)
|
|
38771
38954
|
request2.vendor = { anthropic: extras };
|
|
38955
|
+
const betas = readBetas(headers);
|
|
38956
|
+
if (betas.length > 0)
|
|
38957
|
+
request2.betas = betas;
|
|
38772
38958
|
return validateRequest(request2);
|
|
38773
38959
|
}
|
|
38774
38960
|
|
|
@@ -39002,7 +39188,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
39002
39188
|
keyId = key.id;
|
|
39003
39189
|
rateLimiter.consume(key.id, key.rateLimitPerMin);
|
|
39004
39190
|
const body2 = await request2.json();
|
|
39005
|
-
const chatRequest = surface === "anthropic" ? parseAnthropicRequest(body2) : parseOpenAIRequest(body2);
|
|
39191
|
+
const chatRequest = surface === "anthropic" ? parseAnthropicRequest(body2, request2.headers) : parseOpenAIRequest(body2);
|
|
39006
39192
|
if (key.modelAllowlist !== null && !key.modelAllowlist.includes(chatRequest.model)) {
|
|
39007
39193
|
throw new GatewayError("AUTH", `model "${chatRequest.model}" is not allowed for this API key`);
|
|
39008
39194
|
}
|
|
@@ -39052,7 +39238,11 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
39052
39238
|
}
|
|
39053
39239
|
function proxyRoutes(deps) {
|
|
39054
39240
|
const rateLimiter = deps.rateLimiter ?? new ApiKeyRateLimiter(deps.now);
|
|
39055
|
-
|
|
39241
|
+
const dispatchDeps = {
|
|
39242
|
+
...deps,
|
|
39243
|
+
snapshots: deps.snapshots ?? createRoutingSnapshotCache(deps.store)
|
|
39244
|
+
};
|
|
39245
|
+
return new Elysia().post("/v1/messages", ({ request: request2 }) => handle(dispatchDeps, rateLimiter, "anthropic", request2)).post("/v1/chat/completions", ({ request: request2 }) => handle(dispatchDeps, rateLimiter, "openai", request2)).get("/v1/models", async ({ request: request2 }) => {
|
|
39056
39246
|
try {
|
|
39057
39247
|
const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
|
|
39058
39248
|
const models = await deps.store.config.listModels();
|
|
@@ -39085,6 +39275,7 @@ function createApp(deps) {
|
|
|
39085
39275
|
const adapters = deps.adapters ?? ADAPTERS;
|
|
39086
39276
|
const requestId = deps.requestId ?? (() => `req_${crypto.randomUUID()}`);
|
|
39087
39277
|
const rateLimiter = new ApiKeyRateLimiter(now);
|
|
39278
|
+
const snapshots = createRoutingSnapshotCache(deps.store);
|
|
39088
39279
|
const admin = createAdminAuth(deps.store, { now, sessionTtlMs: ADMIN_SESSION_TTL_MS });
|
|
39089
39280
|
const refresh = deps.refresh ?? createRefresher({
|
|
39090
39281
|
store: deps.store,
|
|
@@ -39101,6 +39292,7 @@ function createApp(deps) {
|
|
|
39101
39292
|
}
|
|
39102
39293
|
return new Elysia().get("/health", () => ({ ok: true })).use(proxyRoutes({
|
|
39103
39294
|
store: deps.store,
|
|
39295
|
+
snapshots,
|
|
39104
39296
|
adapters,
|
|
39105
39297
|
http,
|
|
39106
39298
|
now,
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{B as e,Dt as t,I as n,Kt as r,M as i,Ot as a}from"./queries-vY5lJqBe.js";import{m as o}from"./Rack-
|
|
1
|
+
import{B as e,Dt as t,I as n,Kt as r,M as i,Ot as a}from"./queries-vY5lJqBe.js";import{m as o}from"./Rack-uDxrjvaz.js";var s=o(`check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),c=o(`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`}]]),l=r(a(),1);function u(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 d(e,t){if(typeof navigator<`u`&&navigator.clipboard!==void 0)try{return await navigator.clipboard.writeText(e),`copied`}catch{}let n=u(t??null);if(n&&typeof document.execCommand==`function`)try{if(document.execCommand(`copy`))return`copied`}catch{}return n?`selected`:`failed`}var f=t(),p=e.div`
|
|
2
2
|
display: flex;
|
|
3
3
|
align-items: stretch;
|
|
4
4
|
gap: 0;
|