omnigateway 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/omni.js +140 -51
- package/gateway.js +289 -87
- package/package.json +1 -1
- package/public/assets/{Chip-D0YiqYkz.js → Chip-Ccqsb_Mq.js} +2 -2
- package/public/assets/{CopyValue-bzKz2tiJ.js → CopyValue-BhGx7Gy0.js} +5 -5
- package/public/assets/{Field-DdscQ9OO.js → Field-Devm4K46.js} +8 -8
- package/public/assets/Lamp-BXSUIAtG.js +25 -0
- package/public/assets/{Meter-Bm1oQE2U.js → Meter-Fn8X7t60.js} +2 -2
- package/public/assets/{Modal-Crr8uyWt.js → Modal-DOQGdhoM.js} +8 -8
- package/public/assets/{Rack-uDxrjvaz.js → Rack-DnbGnBvz.js} +18 -18
- package/public/assets/{Readout-CYg6UOop.js → Readout-BKpOZIGX.js} +5 -5
- package/public/assets/{States-DPTTYUHL.js → States-DBzsiHjQ.js} +10 -10
- package/public/assets/{Table-LkPaHBsj.js → Table-DPDJeeVc.js} +1 -1
- package/public/assets/{Toggle-D0e4C94X.js → Toggle-CP3RvAAB.js} +3 -3
- package/public/assets/_app-D83powx4.js +1 -0
- package/public/assets/_app.accounts-DR-2t9-7.js +51 -0
- package/public/assets/_app.index-DimCB0Bo.js +61 -0
- package/public/assets/{_app.keys-BNEOjtbu.js → _app.keys-B7kl3CSe.js} +8 -8
- package/public/assets/_app.logs-BjtdzcDY.js +18 -0
- package/public/assets/_app.models-BEECjcLM.js +144 -0
- package/public/assets/{_app.settings-C1gB4w7c.js → _app.settings-DtDIhAAr.js} +6 -6
- package/public/assets/{_app.usage-B0iHKzmI.js → _app.usage-BChnSBbS.js} +28 -28
- package/public/assets/index-C53PiRfb.js +170 -0
- package/public/assets/{login-D-FsWNX8.js → login-BuvJY1Qd.js} +8 -8
- package/public/assets/{queries-vY5lJqBe.js → queries-BMwc6EvM.js} +1 -1
- package/public/assets/{trash-2-Cep_aVLx.js → trash-2-Cq7ldH9D.js} +1 -1
- package/public/index.html +2 -2
- package/public/assets/Lamp-WOotyOTd.js +0 -15
- package/public/assets/_app-OSvB-Dv9.js +0 -1
- package/public/assets/_app.accounts-BOE49WyS.js +0 -51
- package/public/assets/_app.index-CTRlqxOq.js +0 -61
- package/public/assets/_app.logs-BxxqeEhM.js +0 -18
- package/public/assets/_app.models-BJi-RGUO.js +0 -144
- package/public/assets/index--dA3FFSN.js +0 -170
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
|
|
@@ -22609,6 +22634,27 @@ ALTER TABLE credentials ADD COLUMN disabled_reason TEXT;
|
|
|
22609
22634
|
ALTER TABLE credentials ADD COLUMN disabled_at INTEGER;
|
|
22610
22635
|
`;
|
|
22611
22636
|
|
|
22637
|
+
// packages/store/src/sqlite/migrations/004_request_state.sql
|
|
22638
|
+
var _004_request_state_default = `-- Whether a request is still running.
|
|
22639
|
+
--
|
|
22640
|
+
-- A row used to be written once, after the response stream drained, so a long
|
|
22641
|
+
-- stream was invisible for its whole life and the console showed an idle
|
|
22642
|
+
-- gateway. A row is now written twice: \`pending\` when dispatch starts, \`done\`
|
|
22643
|
+
-- when it finishes.
|
|
22644
|
+
--
|
|
22645
|
+
-- Existing rows default to 'done'. Every row that predates this migration
|
|
22646
|
+
-- describes a request that has already ended, by definition.
|
|
22647
|
+
ALTER TABLE request_logs ADD COLUMN state TEXT NOT NULL DEFAULT 'done';
|
|
22648
|
+
|
|
22649
|
+
-- \`status\` and \`duration_ms\` stay NOT NULL: making them nullable means a table
|
|
22650
|
+
-- rebuild in SQLite, and a pending row's zeros are never read. Readers key off
|
|
22651
|
+
-- \`state\` alone.
|
|
22652
|
+
--
|
|
22653
|
+
-- Partial, because pending rows are a handful at any moment while the table
|
|
22654
|
+
-- holds a month of finished ones.
|
|
22655
|
+
CREATE INDEX idx_request_logs_pending ON request_logs(state) WHERE state = 'pending';
|
|
22656
|
+
`;
|
|
22657
|
+
|
|
22612
22658
|
// packages/store/src/sqlite/rollup.ts
|
|
22613
22659
|
function startOfLocalDay(at) {
|
|
22614
22660
|
const day = new Date(at);
|
|
@@ -22715,7 +22761,8 @@ function backfillDaily(db) {
|
|
|
22715
22761
|
var MIGRATIONS = [
|
|
22716
22762
|
{ id: 1, sql: _001_init_default },
|
|
22717
22763
|
{ id: 2, sql: _002_usage_daily_default, after: backfillDaily },
|
|
22718
|
-
{ id: 3, sql: _003_quota_snapshot_default }
|
|
22764
|
+
{ id: 3, sql: _003_quota_snapshot_default },
|
|
22765
|
+
{ id: 4, sql: _004_request_state_default }
|
|
22719
22766
|
];
|
|
22720
22767
|
function openDb(path) {
|
|
22721
22768
|
const db = new Database(path, { create: true });
|
|
@@ -22787,6 +22834,7 @@ function createKeyRepo(db) {
|
|
|
22787
22834
|
// packages/store/src/sqlite/usage.ts
|
|
22788
22835
|
var toLog = (r) => ({
|
|
22789
22836
|
id: r.id,
|
|
22837
|
+
state: r.state === "pending" ? "pending" : "done",
|
|
22790
22838
|
at: r.at,
|
|
22791
22839
|
apiKeyId: r.api_key_id,
|
|
22792
22840
|
requestedModel: r.requested_model,
|
|
@@ -22845,37 +22893,78 @@ function label(value) {
|
|
|
22845
22893
|
const text = String(value);
|
|
22846
22894
|
return text.length === 0 ? "unknown" : text;
|
|
22847
22895
|
}
|
|
22896
|
+
var COLUMNS = `(id, state, at, api_key_id, requested_model, resolved_provider, resolved_model,
|
|
22897
|
+
credential_id, attempts, status, error_code, input_tokens, output_tokens,
|
|
22898
|
+
cache_read_tokens, cache_write_tokens, ttft_ms, duration_ms, cost_usd,
|
|
22899
|
+
degradations)`;
|
|
22900
|
+
var PLACEHOLDERS = "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
|
|
22901
|
+
function values(log, state) {
|
|
22902
|
+
return [
|
|
22903
|
+
log.id,
|
|
22904
|
+
state,
|
|
22905
|
+
log.at,
|
|
22906
|
+
log.apiKeyId,
|
|
22907
|
+
log.requestedModel,
|
|
22908
|
+
log.resolvedProvider,
|
|
22909
|
+
log.resolvedModel,
|
|
22910
|
+
log.credentialId,
|
|
22911
|
+
log.attempts,
|
|
22912
|
+
log.status,
|
|
22913
|
+
log.errorCode,
|
|
22914
|
+
log.inputTokens,
|
|
22915
|
+
log.outputTokens,
|
|
22916
|
+
log.cacheReadTokens,
|
|
22917
|
+
log.cacheWriteTokens,
|
|
22918
|
+
log.ttftMs,
|
|
22919
|
+
log.durationMs,
|
|
22920
|
+
log.costUsd,
|
|
22921
|
+
JSON.stringify(log.degradations)
|
|
22922
|
+
];
|
|
22923
|
+
}
|
|
22924
|
+
var COMPLETE = `INSERT INTO request_logs ${COLUMNS} VALUES ${PLACEHOLDERS}
|
|
22925
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
22926
|
+
state = 'done',
|
|
22927
|
+
requested_model = COALESCE(NULLIF(excluded.requested_model, ''), request_logs.requested_model),
|
|
22928
|
+
api_key_id = COALESCE(excluded.api_key_id, request_logs.api_key_id),
|
|
22929
|
+
resolved_provider = excluded.resolved_provider,
|
|
22930
|
+
resolved_model = excluded.resolved_model,
|
|
22931
|
+
credential_id = excluded.credential_id,
|
|
22932
|
+
attempts = excluded.attempts,
|
|
22933
|
+
status = excluded.status,
|
|
22934
|
+
error_code = excluded.error_code,
|
|
22935
|
+
input_tokens = excluded.input_tokens,
|
|
22936
|
+
output_tokens = excluded.output_tokens,
|
|
22937
|
+
cache_read_tokens = excluded.cache_read_tokens,
|
|
22938
|
+
cache_write_tokens = excluded.cache_write_tokens,
|
|
22939
|
+
ttft_ms = excluded.ttft_ms,
|
|
22940
|
+
duration_ms = excluded.duration_ms,
|
|
22941
|
+
cost_usd = excluded.cost_usd,
|
|
22942
|
+
degradations = excluded.degradations`;
|
|
22848
22943
|
function createUsageRepo(db) {
|
|
22849
|
-
const
|
|
22850
|
-
db.run(
|
|
22851
|
-
|
|
22852
|
-
|
|
22853
|
-
|
|
22854
|
-
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, [
|
|
22855
|
-
log.id,
|
|
22856
|
-
log.at,
|
|
22857
|
-
log.apiKeyId,
|
|
22858
|
-
log.requestedModel,
|
|
22859
|
-
log.resolvedProvider,
|
|
22860
|
-
log.resolvedModel,
|
|
22861
|
-
log.credentialId,
|
|
22862
|
-
log.attempts,
|
|
22863
|
-
log.status,
|
|
22864
|
-
log.errorCode,
|
|
22865
|
-
log.inputTokens,
|
|
22866
|
-
log.outputTokens,
|
|
22867
|
-
log.cacheReadTokens,
|
|
22868
|
-
log.cacheWriteTokens,
|
|
22869
|
-
log.ttftMs,
|
|
22870
|
-
log.durationMs,
|
|
22871
|
-
log.costUsd,
|
|
22872
|
-
JSON.stringify(log.degradations)
|
|
22873
|
-
]);
|
|
22874
|
-
rollupLog(db, log);
|
|
22944
|
+
const complete = db.transaction((log) => {
|
|
22945
|
+
db.run(COMPLETE, values(log, "done"));
|
|
22946
|
+
const stored = db.query("SELECT * FROM request_logs WHERE id = ?").get(log.id);
|
|
22947
|
+
if (stored !== null)
|
|
22948
|
+
rollupLog(db, toLog(stored));
|
|
22875
22949
|
});
|
|
22876
22950
|
return {
|
|
22951
|
+
async begin(log) {
|
|
22952
|
+
db.run(`INSERT INTO request_logs ${COLUMNS} VALUES ${PLACEHOLDERS}`, values(log, "pending"));
|
|
22953
|
+
},
|
|
22954
|
+
async route(id, target) {
|
|
22955
|
+
db.run(`UPDATE request_logs
|
|
22956
|
+
SET resolved_provider = ?, resolved_model = ?, credential_id = ?
|
|
22957
|
+
WHERE id = ? AND state = 'pending'`, [target.provider, target.model, target.credentialId, id]);
|
|
22958
|
+
},
|
|
22877
22959
|
async append(log) {
|
|
22878
|
-
|
|
22960
|
+
complete(log);
|
|
22961
|
+
},
|
|
22962
|
+
async sweepPending() {
|
|
22963
|
+
const stale = db.query("SELECT * FROM request_logs WHERE state = 'pending'").all().map(toLog);
|
|
22964
|
+
for (const log of stale) {
|
|
22965
|
+
complete({ ...log, status: 499, errorCode: "interrupted", durationMs: 0 });
|
|
22966
|
+
}
|
|
22967
|
+
return stale.length;
|
|
22879
22968
|
},
|
|
22880
22969
|
async recent(limit) {
|
|
22881
22970
|
return db.query("SELECT * FROM request_logs ORDER BY at DESC LIMIT ?").all(limit).map(toLog);
|
|
@@ -22897,7 +22986,7 @@ function createUsageRepo(db) {
|
|
|
22897
22986
|
COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,
|
|
22898
22987
|
COALESCE(SUM(cost_usd), 0) AS cost_usd
|
|
22899
22988
|
FROM ${daily ? "usage_daily" : "request_logs"}
|
|
22900
|
-
WHERE ${timeColumn} >= ? AND ${timeColumn} <= ?
|
|
22989
|
+
WHERE ${daily ? "" : "state = 'done' AND "}${timeColumn} >= ? AND ${timeColumn} <= ?
|
|
22901
22990
|
GROUP BY key${split === null ? "" : ", split"}
|
|
22902
22991
|
ORDER BY requests DESC`).all(since, until);
|
|
22903
22992
|
return rows.map((r) => {
|
|
@@ -31442,17 +31531,17 @@ var ElysiaType = {
|
|
|
31442
31531
|
elysiaMeta: "ArrayString"
|
|
31443
31532
|
})).Decode((value) => {
|
|
31444
31533
|
if (Array.isArray(value)) {
|
|
31445
|
-
let
|
|
31534
|
+
let values2 = [];
|
|
31446
31535
|
for (let i = 0;i < value.length; i++) {
|
|
31447
31536
|
const v = value[i];
|
|
31448
31537
|
if (typeof v == "string") {
|
|
31449
31538
|
const t2 = decode4(v, true);
|
|
31450
|
-
Array.isArray(t2) ?
|
|
31539
|
+
Array.isArray(t2) ? values2 = values2.concat(t2) : values2.push(t2);
|
|
31451
31540
|
continue;
|
|
31452
31541
|
}
|
|
31453
|
-
|
|
31542
|
+
values2.push(v);
|
|
31454
31543
|
}
|
|
31455
|
-
return
|
|
31544
|
+
return values2;
|
|
31456
31545
|
}
|
|
31457
31546
|
return typeof value == "string" ? decode4(value) : value;
|
|
31458
31547
|
}).Encode((value) => {
|
|
@@ -31473,17 +31562,17 @@ var ElysiaType = {
|
|
|
31473
31562
|
elysiaMeta: "ArrayQuery"
|
|
31474
31563
|
})).Decode((value) => {
|
|
31475
31564
|
if (Array.isArray(value)) {
|
|
31476
|
-
let
|
|
31565
|
+
let values2 = [];
|
|
31477
31566
|
for (let i = 0;i < value.length; i++) {
|
|
31478
31567
|
const v = value[i];
|
|
31479
31568
|
if (typeof v == "string") {
|
|
31480
31569
|
const t2 = decode4(v);
|
|
31481
|
-
Array.isArray(t2) ?
|
|
31570
|
+
Array.isArray(t2) ? values2 = values2.concat(t2) : values2.push(t2);
|
|
31482
31571
|
continue;
|
|
31483
31572
|
}
|
|
31484
|
-
|
|
31573
|
+
values2.push(v);
|
|
31485
31574
|
}
|
|
31486
|
-
return
|
|
31575
|
+
return values2;
|
|
31487
31576
|
}
|
|
31488
31577
|
return typeof value == "string" ? decode4(value) : value;
|
|
31489
31578
|
}).Encode((value) => {
|
|
@@ -31527,16 +31616,16 @@ var ElysiaType = {
|
|
|
31527
31616
|
sign
|
|
31528
31617
|
}, v;
|
|
31529
31618
|
},
|
|
31530
|
-
UnionEnum: (
|
|
31531
|
-
const type =
|
|
31532
|
-
if (
|
|
31619
|
+
UnionEnum: (values2, options = {}) => {
|
|
31620
|
+
const type = values2.every((value) => typeof value == "string") ? { type: "string" } : values2.every((value) => typeof value == "number") ? { type: "number" } : values2.every((value) => value === null) ? { type: "null" } : {};
|
|
31621
|
+
if (values2.some((x) => typeof x == "object" && x !== null))
|
|
31533
31622
|
throw new Error("This type does not support objects or arrays");
|
|
31534
31623
|
return {
|
|
31535
|
-
default:
|
|
31624
|
+
default: values2[0],
|
|
31536
31625
|
...options,
|
|
31537
31626
|
[Kind]: "UnionEnum",
|
|
31538
31627
|
...type,
|
|
31539
|
-
enum:
|
|
31628
|
+
enum: values2
|
|
31540
31629
|
};
|
|
31541
31630
|
},
|
|
31542
31631
|
NoValidate: (v, enabled = true) => (v.noValidate = enabled, v),
|
|
@@ -33982,33 +34071,33 @@ var getSchemaValidator = (s, {
|
|
|
33982
34071
|
return v.then((v2) => Check22(v2, true));
|
|
33983
34072
|
if (v.issues)
|
|
33984
34073
|
return v;
|
|
33985
|
-
const
|
|
33986
|
-
return v && typeof v == "object" &&
|
|
33987
|
-
}, runCheckers2 = function(value, startIndex,
|
|
34074
|
+
const values2 = [];
|
|
34075
|
+
return v && typeof v == "object" && values2.push(v.value), runCheckers2(value, 0, values2, v);
|
|
34076
|
+
}, runCheckers2 = function(value, startIndex, values2, lastV) {
|
|
33988
34077
|
for (let i = startIndex;i < checkers.length; i++) {
|
|
33989
34078
|
let v = checkers[i].validate(value);
|
|
33990
34079
|
if (v instanceof Promise)
|
|
33991
34080
|
return v.then((resolved) => {
|
|
33992
34081
|
if (resolved.issues)
|
|
33993
34082
|
return resolved;
|
|
33994
|
-
const nextValues = [...
|
|
34083
|
+
const nextValues = [...values2];
|
|
33995
34084
|
return resolved && typeof resolved == "object" && nextValues.push(resolved.value), runCheckers2(value, i + 1, nextValues, resolved);
|
|
33996
34085
|
});
|
|
33997
34086
|
if (v.issues)
|
|
33998
34087
|
return v;
|
|
33999
|
-
v && typeof v == "object" &&
|
|
34088
|
+
v && typeof v == "object" && values2.push(v.value), lastV = v;
|
|
34000
34089
|
}
|
|
34001
|
-
return mergeValues22(
|
|
34002
|
-
}, mergeValues22 = function(
|
|
34003
|
-
if (!
|
|
34090
|
+
return mergeValues22(values2, lastV);
|
|
34091
|
+
}, mergeValues22 = function(values2, lastV) {
|
|
34092
|
+
if (!values2.length)
|
|
34004
34093
|
return { value: lastV };
|
|
34005
|
-
if (
|
|
34006
|
-
return { value:
|
|
34007
|
-
if (
|
|
34008
|
-
return { value: mergeDeep(
|
|
34009
|
-
let newValue = mergeDeep(
|
|
34010
|
-
for (let i = 2;i <
|
|
34011
|
-
newValue = mergeDeep(newValue,
|
|
34094
|
+
if (values2.length === 1)
|
|
34095
|
+
return { value: values2[0] };
|
|
34096
|
+
if (values2.length === 2)
|
|
34097
|
+
return { value: mergeDeep(values2[0], values2[1]) };
|
|
34098
|
+
let newValue = mergeDeep(values2[0], values2[1]);
|
|
34099
|
+
for (let i = 2;i < values2.length; i++)
|
|
34100
|
+
newValue = mergeDeep(newValue, values2[i]);
|
|
34012
34101
|
return { value: newValue };
|
|
34013
34102
|
};
|
|
34014
34103
|
var Check2 = Check22, runCheckers = runCheckers2, mergeValues2 = mergeValues22;
|
|
@@ -38203,6 +38292,7 @@ async function dispatch(request2, deps, signal) {
|
|
|
38203
38292
|
const deadlineAt = startedAt + snapshot.settings.requestDeadlineMs;
|
|
38204
38293
|
const log = {
|
|
38205
38294
|
id: crypto.randomUUID(),
|
|
38295
|
+
state: "done",
|
|
38206
38296
|
at: startedAt,
|
|
38207
38297
|
apiKeyId: null,
|
|
38208
38298
|
requestedModel: request2.model,
|
|
@@ -38294,6 +38384,11 @@ async function dispatch(request2, deps, signal) {
|
|
|
38294
38384
|
log.credentialId = candidate.credential.id;
|
|
38295
38385
|
log.resolvedProvider = candidate.target.provider;
|
|
38296
38386
|
log.resolvedModel = candidate.target.model;
|
|
38387
|
+
await deps.onRoute?.({
|
|
38388
|
+
provider: candidate.target.provider,
|
|
38389
|
+
model: candidate.target.model,
|
|
38390
|
+
credentialId: candidate.credential.id
|
|
38391
|
+
});
|
|
38297
38392
|
log.inputTokens = 0;
|
|
38298
38393
|
log.outputTokens = 0;
|
|
38299
38394
|
log.cacheReadTokens = 0;
|
|
@@ -38319,12 +38414,18 @@ async function dispatch(request2, deps, signal) {
|
|
|
38319
38414
|
}), dispatchSignal);
|
|
38320
38415
|
for (const d of result.degradations)
|
|
38321
38416
|
log.degradations.push(d);
|
|
38417
|
+
let terminal = false;
|
|
38418
|
+
const pending = [];
|
|
38322
38419
|
for await (const event of result.events) {
|
|
38323
38420
|
if (event.type === "blockDelta" && !committed) {
|
|
38324
38421
|
committed = true;
|
|
38325
38422
|
log.ttftMs = deps.now() - startedAt;
|
|
38423
|
+
for (const buffered of pending)
|
|
38424
|
+
yield buffered;
|
|
38425
|
+
pending.length = 0;
|
|
38326
38426
|
}
|
|
38327
38427
|
if (event.type === "end") {
|
|
38428
|
+
terminal = true;
|
|
38328
38429
|
log.inputTokens = event.usage.inputTokens;
|
|
38329
38430
|
log.outputTokens = event.usage.outputTokens;
|
|
38330
38431
|
log.cacheReadTokens = event.usage.cacheReadTokens;
|
|
@@ -38332,6 +38433,7 @@ async function dispatch(request2, deps, signal) {
|
|
|
38332
38433
|
log.costUsd = priceOf(candidate, event.usage);
|
|
38333
38434
|
}
|
|
38334
38435
|
if (event.type === "error") {
|
|
38436
|
+
terminal = true;
|
|
38335
38437
|
if (!committed && RETRYABLE[event.code]) {
|
|
38336
38438
|
throw new GatewayError(event.code, event.message);
|
|
38337
38439
|
}
|
|
@@ -38348,7 +38450,21 @@ async function dispatch(request2, deps, signal) {
|
|
|
38348
38450
|
log.durationMs = deps.now() - startedAt;
|
|
38349
38451
|
return;
|
|
38350
38452
|
}
|
|
38351
|
-
|
|
38453
|
+
if (!committed && event.type !== "end") {
|
|
38454
|
+
pending.push(event);
|
|
38455
|
+
} else {
|
|
38456
|
+
if (!committed) {
|
|
38457
|
+
for (const buffered of pending)
|
|
38458
|
+
yield buffered;
|
|
38459
|
+
pending.length = 0;
|
|
38460
|
+
}
|
|
38461
|
+
yield event;
|
|
38462
|
+
}
|
|
38463
|
+
if (event.type === "end")
|
|
38464
|
+
break;
|
|
38465
|
+
}
|
|
38466
|
+
if (!terminal) {
|
|
38467
|
+
throw new GatewayError("UPSTREAM", "upstream stream ended without a terminal event");
|
|
38352
38468
|
}
|
|
38353
38469
|
await persistHealth(recordSuccess(healthFor(candidate), {
|
|
38354
38470
|
settings: snapshot.settings,
|
|
@@ -38602,6 +38718,7 @@ var chunk = (id, created, model, choice, usage) => ({
|
|
|
38602
38718
|
async function* openaiStream(events, requestId, created) {
|
|
38603
38719
|
let model = "";
|
|
38604
38720
|
let roleSent = false;
|
|
38721
|
+
let completed = false;
|
|
38605
38722
|
const toolIndex = new Map;
|
|
38606
38723
|
const emit = (data) => ({ event: "message", data: JSON.stringify(data) });
|
|
38607
38724
|
for await (const event of events) {
|
|
@@ -38663,6 +38780,7 @@ async function* openaiStream(events, requestId, created) {
|
|
|
38663
38780
|
case "blockEnd":
|
|
38664
38781
|
break;
|
|
38665
38782
|
case "end":
|
|
38783
|
+
completed = true;
|
|
38666
38784
|
yield emit(chunk(requestId, created, model, { index: 0, delta: {}, finish_reason: FINISH2[event.stopReason] }, {
|
|
38667
38785
|
prompt_tokens: event.usage.inputTokens,
|
|
38668
38786
|
completion_tokens: event.usage.outputTokens,
|
|
@@ -38676,7 +38794,8 @@ async function* openaiStream(events, requestId, created) {
|
|
|
38676
38794
|
}
|
|
38677
38795
|
}
|
|
38678
38796
|
}
|
|
38679
|
-
|
|
38797
|
+
if (completed)
|
|
38798
|
+
yield { event: "message", data: "[DONE]" };
|
|
38680
38799
|
}
|
|
38681
38800
|
function openaiResponse(collected, requestId, created) {
|
|
38682
38801
|
const text = collected.content.flatMap((b) => b.type === "text" ? [b.text] : []).join("");
|
|
@@ -38855,7 +38974,14 @@ function toIrReasoning(thinking, effort) {
|
|
|
38855
38974
|
return { mode: "off" };
|
|
38856
38975
|
}
|
|
38857
38976
|
}
|
|
38858
|
-
function
|
|
38977
|
+
function readBetas(headers) {
|
|
38978
|
+
const raw = headers?.get("anthropic-beta");
|
|
38979
|
+
if (raw === undefined || raw === null)
|
|
38980
|
+
return [];
|
|
38981
|
+
const names = raw.split(",").map((name) => name.trim()).filter((name) => name.length > 0);
|
|
38982
|
+
return [...new Set(names)];
|
|
38983
|
+
}
|
|
38984
|
+
function parseAnthropicRequest(body2, headers) {
|
|
38859
38985
|
if (typeof body2 !== "object" || body2 === null) {
|
|
38860
38986
|
throw new GatewayError("BAD_REQUEST", "request body must be a JSON object");
|
|
38861
38987
|
}
|
|
@@ -38896,6 +39022,9 @@ function parseAnthropicRequest(body2) {
|
|
|
38896
39022
|
const extras = extraFields(body2, KNOWN);
|
|
38897
39023
|
if (extras !== undefined)
|
|
38898
39024
|
request2.vendor = { anthropic: extras };
|
|
39025
|
+
const betas = readBetas(headers);
|
|
39026
|
+
if (betas.length > 0)
|
|
39027
|
+
request2.betas = betas;
|
|
38899
39028
|
return validateRequest(request2);
|
|
38900
39029
|
}
|
|
38901
39030
|
|
|
@@ -39057,18 +39186,44 @@ function parseOpenAIRequest(body2) {
|
|
|
39057
39186
|
}
|
|
39058
39187
|
|
|
39059
39188
|
// apps/gateway/src/logging.ts
|
|
39189
|
+
function report(what, requestId, error51) {
|
|
39190
|
+
console.error(what, {
|
|
39191
|
+
requestId,
|
|
39192
|
+
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
39193
|
+
});
|
|
39194
|
+
}
|
|
39195
|
+
async function beginLog(store, log, keyId) {
|
|
39196
|
+
try {
|
|
39197
|
+
await store.usage.begin({ ...log, state: "pending", apiKeyId: keyId });
|
|
39198
|
+
} catch (error51) {
|
|
39199
|
+
report("failed to record request start", log.id, error51);
|
|
39200
|
+
}
|
|
39201
|
+
}
|
|
39202
|
+
async function routeLog(store, requestId, target) {
|
|
39203
|
+
try {
|
|
39204
|
+
await store.usage.route(requestId, target);
|
|
39205
|
+
} catch (error51) {
|
|
39206
|
+
report("failed to record request route", requestId, error51);
|
|
39207
|
+
}
|
|
39208
|
+
}
|
|
39060
39209
|
async function finishLog(store, log, keyId) {
|
|
39061
39210
|
try {
|
|
39062
39211
|
await store.usage.append({ ...log, apiKeyId: keyId });
|
|
39063
39212
|
} catch (error51) {
|
|
39064
|
-
|
|
39065
|
-
requestId: log.id,
|
|
39066
|
-
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
39067
|
-
});
|
|
39213
|
+
report("failed to persist request log", log.id, error51);
|
|
39068
39214
|
}
|
|
39069
39215
|
}
|
|
39070
39216
|
|
|
39071
39217
|
// apps/gateway/src/routes/proxy.ts
|
|
39218
|
+
var KEEPALIVE_MS = 1e4;
|
|
39219
|
+
var KEEPALIVE = Symbol("keepalive");
|
|
39220
|
+
function withKeepalive(pending, ms) {
|
|
39221
|
+
let timer;
|
|
39222
|
+
const tick = new Promise((resolve) => {
|
|
39223
|
+
timer = setTimeout(() => resolve(KEEPALIVE), ms);
|
|
39224
|
+
});
|
|
39225
|
+
return Promise.race([pending, tick]).finally(() => clearTimeout(timer));
|
|
39226
|
+
}
|
|
39072
39227
|
var SSE_HEADERS = {
|
|
39073
39228
|
"content-type": "text/event-stream; charset=utf-8",
|
|
39074
39229
|
"cache-control": "no-cache, no-transform",
|
|
@@ -39087,7 +39242,7 @@ function asGatewayError2(error51) {
|
|
|
39087
39242
|
return error51;
|
|
39088
39243
|
return new GatewayError("INTERNAL", error51 instanceof Error ? error51.message : "internal error");
|
|
39089
39244
|
}
|
|
39090
|
-
function sseResponse(frames, onDone) {
|
|
39245
|
+
function sseResponse(frames, onDone, keepaliveMs) {
|
|
39091
39246
|
const encoder2 = new TextEncoder;
|
|
39092
39247
|
let done = null;
|
|
39093
39248
|
const runOnce = () => {
|
|
@@ -39095,10 +39250,19 @@ function sseResponse(frames, onDone) {
|
|
|
39095
39250
|
done = onDone();
|
|
39096
39251
|
return done;
|
|
39097
39252
|
};
|
|
39253
|
+
let inflight = null;
|
|
39098
39254
|
const stream2 = new ReadableStream({
|
|
39099
39255
|
async pull(controller) {
|
|
39100
39256
|
try {
|
|
39101
|
-
|
|
39257
|
+
inflight ??= frames.next();
|
|
39258
|
+
const next = await withKeepalive(inflight, keepaliveMs);
|
|
39259
|
+
if (next === KEEPALIVE) {
|
|
39260
|
+
controller.enqueue(encoder2.encode(`: keepalive
|
|
39261
|
+
|
|
39262
|
+
`));
|
|
39263
|
+
return;
|
|
39264
|
+
}
|
|
39265
|
+
inflight = null;
|
|
39102
39266
|
if (next.done === true) {
|
|
39103
39267
|
controller.close();
|
|
39104
39268
|
await runOnce();
|
|
@@ -39123,21 +39287,54 @@ data: ${data}
|
|
|
39123
39287
|
}
|
|
39124
39288
|
async function handle(deps, rateLimiter, surface, request2) {
|
|
39125
39289
|
const requestId = deps.requestId();
|
|
39290
|
+
const startedAt = deps.now();
|
|
39126
39291
|
let keyId = null;
|
|
39292
|
+
let requestedModel = "";
|
|
39127
39293
|
try {
|
|
39128
39294
|
const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
|
|
39129
39295
|
keyId = key.id;
|
|
39130
39296
|
rateLimiter.consume(key.id, key.rateLimitPerMin);
|
|
39131
39297
|
const body2 = await request2.json();
|
|
39132
|
-
const chatRequest = surface === "anthropic" ? parseAnthropicRequest(body2) : parseOpenAIRequest(body2);
|
|
39298
|
+
const chatRequest = surface === "anthropic" ? parseAnthropicRequest(body2, request2.headers) : parseOpenAIRequest(body2);
|
|
39133
39299
|
if (key.modelAllowlist !== null && !key.modelAllowlist.includes(chatRequest.model)) {
|
|
39134
39300
|
throw new GatewayError("AUTH", `model "${chatRequest.model}" is not allowed for this API key`);
|
|
39135
39301
|
}
|
|
39136
|
-
|
|
39302
|
+
requestedModel = chatRequest.model;
|
|
39303
|
+
let began = false;
|
|
39304
|
+
const outcome = await dispatch(chatRequest, {
|
|
39305
|
+
...deps,
|
|
39306
|
+
async onRoute(target) {
|
|
39307
|
+
if (began) {
|
|
39308
|
+
await routeLog(deps.store, requestId, target);
|
|
39309
|
+
return;
|
|
39310
|
+
}
|
|
39311
|
+
began = true;
|
|
39312
|
+
await beginLog(deps.store, {
|
|
39313
|
+
id: requestId,
|
|
39314
|
+
at: startedAt,
|
|
39315
|
+
apiKeyId: keyId,
|
|
39316
|
+
requestedModel,
|
|
39317
|
+
resolvedProvider: target.provider,
|
|
39318
|
+
resolvedModel: target.model,
|
|
39319
|
+
credentialId: target.credentialId,
|
|
39320
|
+
attempts: 0,
|
|
39321
|
+
status: 0,
|
|
39322
|
+
errorCode: null,
|
|
39323
|
+
inputTokens: 0,
|
|
39324
|
+
outputTokens: 0,
|
|
39325
|
+
cacheReadTokens: 0,
|
|
39326
|
+
cacheWriteTokens: 0,
|
|
39327
|
+
ttftMs: null,
|
|
39328
|
+
durationMs: 0,
|
|
39329
|
+
costUsd: 0,
|
|
39330
|
+
degradations: []
|
|
39331
|
+
}, keyId);
|
|
39332
|
+
}
|
|
39333
|
+
}, request2.signal);
|
|
39137
39334
|
const log = () => finishLog(deps.store, { ...outcome.log(), id: requestId }, keyId);
|
|
39138
39335
|
if (chatRequest.stream) {
|
|
39139
39336
|
const frames = surface === "anthropic" ? anthropicStream(outcome.events, requestId) : openaiStream(outcome.events, requestId, Math.floor(deps.now() / 1000));
|
|
39140
|
-
return sseResponse(frames, log);
|
|
39337
|
+
return sseResponse(frames, log, deps.keepaliveMs);
|
|
39141
39338
|
}
|
|
39142
39339
|
const events = [];
|
|
39143
39340
|
for await (const event of outcome.events)
|
|
@@ -39156,9 +39353,10 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
39156
39353
|
const gatewayError = asGatewayError2(error51);
|
|
39157
39354
|
await finishLog(deps.store, {
|
|
39158
39355
|
id: requestId,
|
|
39159
|
-
|
|
39356
|
+
state: "done",
|
|
39357
|
+
at: startedAt,
|
|
39160
39358
|
apiKeyId: keyId,
|
|
39161
|
-
requestedModel
|
|
39359
|
+
requestedModel,
|
|
39162
39360
|
resolvedProvider: null,
|
|
39163
39361
|
resolvedModel: null,
|
|
39164
39362
|
credentialId: null,
|
|
@@ -39181,7 +39379,8 @@ function proxyRoutes(deps) {
|
|
|
39181
39379
|
const rateLimiter = deps.rateLimiter ?? new ApiKeyRateLimiter(deps.now);
|
|
39182
39380
|
const dispatchDeps = {
|
|
39183
39381
|
...deps,
|
|
39184
|
-
snapshots: deps.snapshots ?? createRoutingSnapshotCache(deps.store)
|
|
39382
|
+
snapshots: deps.snapshots ?? createRoutingSnapshotCache(deps.store),
|
|
39383
|
+
keepaliveMs: deps.keepaliveMs ?? KEEPALIVE_MS
|
|
39185
39384
|
};
|
|
39186
39385
|
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 }) => {
|
|
39187
39386
|
try {
|
|
@@ -39410,6 +39609,9 @@ var store = await createStore({
|
|
|
39410
39609
|
path: config2.databasePath,
|
|
39411
39610
|
encryptionKey
|
|
39412
39611
|
});
|
|
39612
|
+
var swept = await store.usage.sweepPending();
|
|
39613
|
+
if (swept > 0)
|
|
39614
|
+
console.log(`retired ${swept} request(s) interrupted by the last shutdown`);
|
|
39413
39615
|
var now = () => Date.now();
|
|
39414
39616
|
var http = nodeHttpClient();
|
|
39415
39617
|
var refresh = createRefresher({ store, providers: OAUTH_PROVIDERS, http, now });
|
|
@@ -39430,7 +39632,7 @@ var stopQuotaPoller = await startQuotaPoller({
|
|
|
39430
39632
|
refresh,
|
|
39431
39633
|
now
|
|
39432
39634
|
});
|
|
39433
|
-
app.listen({ port: config2.port, hostname: config2.host });
|
|
39635
|
+
app.listen({ port: config2.port, hostname: config2.host, idleTimeout: 255 });
|
|
39434
39636
|
console.log(`omnigateway listening on http://${config2.host}:${config2.port}`);
|
|
39435
39637
|
var shuttingDown = false;
|
|
39436
39638
|
function exitAfterClosingStore(code) {
|