omnigateway 0.10.3 → 0.11.0
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 +20 -2
- package/bin/omni.js +440 -301
- package/gateway.js +579 -358
- package/package.json +1 -1
- package/public/assets/{Chip-BeKVtewW.js → Chip-CmL-9rw7.js} +1 -1
- package/public/assets/{CopyValue-NTHhiCuT.js → CopyValue-CTVbU0O_.js} +1 -1
- package/public/assets/{Lamp-C2Q3t5Mn.js → Lamp-DtHKJH6P.js} +1 -1
- package/public/assets/{Rack-sUGRoZUx.js → Rack-_A_3zqxC.js} +1 -1
- package/public/assets/{RequestTable-C2RLARWy.js → RequestTable-Cokk7wKP.js} +1 -1
- package/public/assets/{SummaryDeck-BHXcb_Ji.js → SummaryDeck-C0IbqnLz.js} +1 -1
- package/public/assets/{Toggle-DwKz1aVq.js → Toggle-ChjN-tw1.js} +1 -1
- package/public/assets/{TokenBreakdown-DM-1HvOr.js → TokenBreakdown-BPoXM3JM.js} +1 -1
- package/public/assets/{WindowChart-BsonO5Py.js → WindowChart-BPEsEGFL.js} +1 -1
- package/public/assets/_app.accounts-DKmxz7Tl.js +59 -0
- package/public/assets/{_app.console-BQJMLRv4.js → _app.console-Bkdallia.js} +1 -1
- package/public/assets/{_app.database-BIiajZm6.js → _app.database-DxqN1395.js} +1 -1
- package/public/assets/{_app.index-DagK0Pqp.js → _app.index-BUqor-qH.js} +3 -3
- package/public/assets/{_app.keys-DLAG_bmt.js → _app.keys-DMCPEj1g.js} +1 -1
- package/public/assets/{_app.logs-AyVq3lLb.js → _app.logs-DGQD8ZAq.js} +1 -1
- package/public/assets/{_app.models-CwlkGPrx.js → _app.models-D6SV_GVk.js} +1 -1
- package/public/assets/{_app.plugins._pluginId-BhvEnawY.js → _app.plugins._pluginId-CJ7ObXXe.js} +1 -1
- package/public/assets/{_app.settings-BKA-1YLs.js → _app.settings-BVimpngR.js} +1 -1
- package/public/assets/{_app.usage-C7JEWKZM.js → _app.usage-Y2Nkaw0t.js} +1 -1
- package/public/assets/{chevron-right-S84SGCvk.js → chevron-right-BfnK63GB.js} +1 -1
- package/public/assets/{client-DCvpw0LS.js → client-WgQ5A2PD.js} +1 -1
- package/public/assets/{index-pZbadEun.js → index-BTE8vyBz.js} +3 -3
- package/public/assets/{login-CitmheGz.js → login-CrJK9wRA.js} +1 -1
- package/public/assets/plus-Cv51cK5k.js +1 -0
- package/public/assets/{preload-helper-BC9bKUg1.js → preload-helper-BknjH67x.js} +1 -1
- package/public/assets/{shared-C47BfV_u.js → shared-oTMOLWMT.js} +1 -1
- package/public/assets/{trash-2-DmtElUHr.js → trash-2-CSkf6ME_.js} +1 -1
- package/public/index.html +4 -4
- package/public/assets/_app.accounts-DUYxItf0.js +0 -59
- package/public/assets/plus-E7qLz9CF.js +0 -1
package/bin/omni.js
CHANGED
|
@@ -19238,13 +19238,13 @@ function prepareArtifact(input2) {
|
|
|
19238
19238
|
error: error61.value
|
|
19239
19239
|
};
|
|
19240
19240
|
const json2 = JSON.stringify(bounded);
|
|
19241
|
-
const size =
|
|
19241
|
+
const size = Buffer.byteLength(json2);
|
|
19242
19242
|
if (size <= MAX_ARTIFACT_BYTES)
|
|
19243
19243
|
return { artifact: bounded, json: json2 };
|
|
19244
19244
|
const marker = omission(size);
|
|
19245
19245
|
const omitted = omitBodies(bounded, marker);
|
|
19246
19246
|
const omittedJson = JSON.stringify(omitted);
|
|
19247
|
-
if (
|
|
19247
|
+
if (Buffer.byteLength(omittedJson) <= MAX_ARTIFACT_BYTES) {
|
|
19248
19248
|
return { artifact: omitted, json: omittedJson };
|
|
19249
19249
|
}
|
|
19250
19250
|
const stripped = { ...omitted, error: marker };
|
|
@@ -19504,6 +19504,261 @@ function estimateCachedInputTokens(request) {
|
|
|
19504
19504
|
}
|
|
19505
19505
|
return cached2;
|
|
19506
19506
|
}
|
|
19507
|
+
// packages/store/src/sqlite/rollup.ts
|
|
19508
|
+
var HOUR_MS = 3600000;
|
|
19509
|
+
function hourOf(at) {
|
|
19510
|
+
return Math.floor(at / HOUR_MS);
|
|
19511
|
+
}
|
|
19512
|
+
var DAY_MS = 86400000;
|
|
19513
|
+
function hostDayOffsetMinutes() {
|
|
19514
|
+
return -new Date().getTimezoneOffset() || 0;
|
|
19515
|
+
}
|
|
19516
|
+
function startOfDay(at, offsetMinutes) {
|
|
19517
|
+
const offsetMs = offsetMinutes * 60000;
|
|
19518
|
+
return Math.floor((at + offsetMs) / DAY_MS) * DAY_MS - offsetMs;
|
|
19519
|
+
}
|
|
19520
|
+
var UPSERT = `
|
|
19521
|
+
INSERT INTO usage_daily
|
|
19522
|
+
(day, provider, credential_id, requested_model, resolved_model, api_key_id,
|
|
19523
|
+
requests, errors, input_tokens, output_tokens, cache_read_tokens,
|
|
19524
|
+
cache_write_tokens, rtk_saved_tokens, rtk_applied_requests, cost_usd, duration_ms_sum)
|
|
19525
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
19526
|
+
ON CONFLICT (day, provider, credential_id, requested_model, resolved_model, api_key_id)
|
|
19527
|
+
DO UPDATE SET
|
|
19528
|
+
requests = requests + excluded.requests,
|
|
19529
|
+
errors = errors + excluded.errors,
|
|
19530
|
+
input_tokens = input_tokens + excluded.input_tokens,
|
|
19531
|
+
output_tokens = output_tokens + excluded.output_tokens,
|
|
19532
|
+
cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
|
|
19533
|
+
cache_write_tokens = cache_write_tokens + excluded.cache_write_tokens,
|
|
19534
|
+
rtk_saved_tokens = rtk_saved_tokens + excluded.rtk_saved_tokens,
|
|
19535
|
+
rtk_applied_requests = rtk_applied_requests + excluded.rtk_applied_requests,
|
|
19536
|
+
cost_usd = cost_usd + excluded.cost_usd,
|
|
19537
|
+
duration_ms_sum = duration_ms_sum + excluded.duration_ms_sum`;
|
|
19538
|
+
function keyOf(log, dayOffsetMinutes) {
|
|
19539
|
+
return [
|
|
19540
|
+
startOfDay(log.at, dayOffsetMinutes),
|
|
19541
|
+
log.resolvedProvider ?? "",
|
|
19542
|
+
log.credentialId ?? "",
|
|
19543
|
+
log.requestedModel,
|
|
19544
|
+
log.resolvedModel ?? "",
|
|
19545
|
+
log.apiKeyId ?? ""
|
|
19546
|
+
];
|
|
19547
|
+
}
|
|
19548
|
+
function countersOf(log) {
|
|
19549
|
+
return {
|
|
19550
|
+
requests: 1,
|
|
19551
|
+
errors: log.status >= 400 ? 1 : 0,
|
|
19552
|
+
inputTokens: log.inputTokens,
|
|
19553
|
+
outputTokens: log.outputTokens,
|
|
19554
|
+
cacheReadTokens: log.cacheReadTokens,
|
|
19555
|
+
cacheWriteTokens: log.cacheWriteTokens,
|
|
19556
|
+
rtkSavedTokens: log.rtkEstimatedTokensSaved,
|
|
19557
|
+
rtkAppliedRequests: log.rtkApplied ? 1 : 0,
|
|
19558
|
+
costUsd: log.costUsd,
|
|
19559
|
+
durationMsSum: log.durationMs
|
|
19560
|
+
};
|
|
19561
|
+
}
|
|
19562
|
+
var BACKFILL_UPSERT = `
|
|
19563
|
+
INSERT INTO usage_daily
|
|
19564
|
+
(day, provider, credential_id, requested_model, resolved_model, api_key_id,
|
|
19565
|
+
requests, errors, input_tokens, output_tokens, cache_read_tokens,
|
|
19566
|
+
cache_write_tokens, cost_usd, duration_ms_sum)
|
|
19567
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
19568
|
+
ON CONFLICT (day, provider, credential_id, requested_model, resolved_model, api_key_id)
|
|
19569
|
+
DO UPDATE SET
|
|
19570
|
+
requests = requests + excluded.requests,
|
|
19571
|
+
errors = errors + excluded.errors,
|
|
19572
|
+
input_tokens = input_tokens + excluded.input_tokens,
|
|
19573
|
+
output_tokens = output_tokens + excluded.output_tokens,
|
|
19574
|
+
cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
|
|
19575
|
+
cache_write_tokens = cache_write_tokens + excluded.cache_write_tokens,
|
|
19576
|
+
cost_usd = cost_usd + excluded.cost_usd,
|
|
19577
|
+
duration_ms_sum = duration_ms_sum + excluded.duration_ms_sum`;
|
|
19578
|
+
function upsertPreRtk(db, key, c) {
|
|
19579
|
+
db.run(BACKFILL_UPSERT, [
|
|
19580
|
+
...key,
|
|
19581
|
+
c.requests,
|
|
19582
|
+
c.errors,
|
|
19583
|
+
c.inputTokens,
|
|
19584
|
+
c.outputTokens,
|
|
19585
|
+
c.cacheReadTokens,
|
|
19586
|
+
c.cacheWriteTokens,
|
|
19587
|
+
c.costUsd,
|
|
19588
|
+
c.durationMsSum
|
|
19589
|
+
]);
|
|
19590
|
+
}
|
|
19591
|
+
function upsert(db, key, c) {
|
|
19592
|
+
db.run(UPSERT, [
|
|
19593
|
+
...key,
|
|
19594
|
+
c.requests,
|
|
19595
|
+
c.errors,
|
|
19596
|
+
c.inputTokens,
|
|
19597
|
+
c.outputTokens,
|
|
19598
|
+
c.cacheReadTokens,
|
|
19599
|
+
c.cacheWriteTokens,
|
|
19600
|
+
c.rtkSavedTokens,
|
|
19601
|
+
c.rtkAppliedRequests,
|
|
19602
|
+
c.costUsd,
|
|
19603
|
+
c.durationMsSum
|
|
19604
|
+
]);
|
|
19605
|
+
}
|
|
19606
|
+
function rollupLog(db, log, dayOffsetMinutes) {
|
|
19607
|
+
upsert(db, keyOf(log, dayOffsetMinutes), countersOf(log));
|
|
19608
|
+
}
|
|
19609
|
+
function backfillDaily(db, dayOffsetMinutes) {
|
|
19610
|
+
const groups = new Map;
|
|
19611
|
+
for (const row of db.query(`SELECT at, api_key_id, requested_model, resolved_provider, resolved_model, credential_id,
|
|
19612
|
+
status, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
19613
|
+
duration_ms, cost_usd
|
|
19614
|
+
FROM request_logs`).all()) {
|
|
19615
|
+
const key = [
|
|
19616
|
+
startOfDay(row.at, dayOffsetMinutes),
|
|
19617
|
+
row.resolved_provider ?? "",
|
|
19618
|
+
row.credential_id ?? "",
|
|
19619
|
+
row.requested_model,
|
|
19620
|
+
row.resolved_model ?? "",
|
|
19621
|
+
row.api_key_id ?? ""
|
|
19622
|
+
];
|
|
19623
|
+
const id = key.join("\x00");
|
|
19624
|
+
const seen = groups.get(id);
|
|
19625
|
+
const counters = seen?.counters ?? {
|
|
19626
|
+
requests: 0,
|
|
19627
|
+
errors: 0,
|
|
19628
|
+
inputTokens: 0,
|
|
19629
|
+
outputTokens: 0,
|
|
19630
|
+
cacheReadTokens: 0,
|
|
19631
|
+
cacheWriteTokens: 0,
|
|
19632
|
+
rtkSavedTokens: 0,
|
|
19633
|
+
rtkAppliedRequests: 0,
|
|
19634
|
+
costUsd: 0,
|
|
19635
|
+
durationMsSum: 0
|
|
19636
|
+
};
|
|
19637
|
+
counters.requests += 1;
|
|
19638
|
+
counters.errors += row.status >= 400 ? 1 : 0;
|
|
19639
|
+
counters.inputTokens += row.input_tokens;
|
|
19640
|
+
counters.outputTokens += row.output_tokens;
|
|
19641
|
+
counters.cacheReadTokens += row.cache_read_tokens;
|
|
19642
|
+
counters.cacheWriteTokens += row.cache_write_tokens;
|
|
19643
|
+
counters.costUsd += row.cost_usd;
|
|
19644
|
+
counters.durationMsSum += row.duration_ms;
|
|
19645
|
+
if (seen === undefined)
|
|
19646
|
+
groups.set(id, { key, counters });
|
|
19647
|
+
}
|
|
19648
|
+
for (const group of groups.values())
|
|
19649
|
+
upsertPreRtk(db, group.key, group.counters);
|
|
19650
|
+
return groups.size;
|
|
19651
|
+
}
|
|
19652
|
+
var HOURLY_UPSERT = `
|
|
19653
|
+
INSERT INTO usage_rollup
|
|
19654
|
+
(api_key_id, hour, requests, input_tokens, output_tokens,
|
|
19655
|
+
cache_read_tokens, cache_write_tokens, cost_usd)
|
|
19656
|
+
VALUES (?,?,?,?,?,?,?,?)
|
|
19657
|
+
ON CONFLICT (api_key_id, hour)
|
|
19658
|
+
DO UPDATE SET
|
|
19659
|
+
requests = requests + excluded.requests,
|
|
19660
|
+
input_tokens = input_tokens + excluded.input_tokens,
|
|
19661
|
+
output_tokens = output_tokens + excluded.output_tokens,
|
|
19662
|
+
cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
|
|
19663
|
+
cache_write_tokens = cache_write_tokens + excluded.cache_write_tokens,
|
|
19664
|
+
cost_usd = cost_usd + excluded.cost_usd`;
|
|
19665
|
+
function rollupHour(db, log) {
|
|
19666
|
+
const apiKeyId = log.apiKeyId;
|
|
19667
|
+
if (apiKeyId === null)
|
|
19668
|
+
return;
|
|
19669
|
+
db.run(HOURLY_UPSERT, [
|
|
19670
|
+
apiKeyId,
|
|
19671
|
+
hourOf(log.at),
|
|
19672
|
+
1,
|
|
19673
|
+
log.inputTokens,
|
|
19674
|
+
log.outputTokens,
|
|
19675
|
+
log.cacheReadTokens,
|
|
19676
|
+
log.cacheWriteTokens,
|
|
19677
|
+
log.costUsd
|
|
19678
|
+
]);
|
|
19679
|
+
}
|
|
19680
|
+
var REBUILD = `
|
|
19681
|
+
INSERT INTO usage_rollup
|
|
19682
|
+
(api_key_id, hour, requests, input_tokens, output_tokens,
|
|
19683
|
+
cache_read_tokens, cache_write_tokens, cost_usd)
|
|
19684
|
+
SELECT api_key_id,
|
|
19685
|
+
CAST(at / 3600000 AS INTEGER),
|
|
19686
|
+
COUNT(*),
|
|
19687
|
+
COALESCE(SUM(input_tokens), 0),
|
|
19688
|
+
COALESCE(SUM(output_tokens), 0),
|
|
19689
|
+
COALESCE(SUM(cache_read_tokens), 0),
|
|
19690
|
+
COALESCE(SUM(cache_write_tokens), 0),
|
|
19691
|
+
COALESCE(SUM(cost_usd), 0)
|
|
19692
|
+
FROM request_logs
|
|
19693
|
+
WHERE state = 'done' AND api_key_id IS NOT NULL
|
|
19694
|
+
GROUP BY api_key_id, CAST(at / 3600000 AS INTEGER)`;
|
|
19695
|
+
function rebuildRollup(db) {
|
|
19696
|
+
db.run("DELETE FROM usage_rollup");
|
|
19697
|
+
db.run(REBUILD);
|
|
19698
|
+
}
|
|
19699
|
+
function auditRollup(db) {
|
|
19700
|
+
const row = db.query(`WITH truth AS (
|
|
19701
|
+
SELECT api_key_id AS k,
|
|
19702
|
+
CAST(at / 3600000 AS INTEGER) AS h,
|
|
19703
|
+
COUNT(*) AS requests,
|
|
19704
|
+
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
19705
|
+
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
19706
|
+
COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,
|
|
19707
|
+
COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,
|
|
19708
|
+
COALESCE(SUM(cost_usd), 0) AS cost_usd
|
|
19709
|
+
FROM request_logs
|
|
19710
|
+
WHERE state = 'done' AND api_key_id IS NOT NULL
|
|
19711
|
+
GROUP BY k, h
|
|
19712
|
+
)
|
|
19713
|
+
SELECT (SELECT COUNT(*) FROM truth) AS buckets,
|
|
19714
|
+
(SELECT COUNT(*)
|
|
19715
|
+
FROM truth
|
|
19716
|
+
LEFT JOIN usage_rollup r ON r.api_key_id = truth.k AND r.hour = truth.h
|
|
19717
|
+
WHERE r.api_key_id IS NULL
|
|
19718
|
+
OR r.requests <> truth.requests
|
|
19719
|
+
OR r.input_tokens <> truth.input_tokens
|
|
19720
|
+
OR r.output_tokens <> truth.output_tokens
|
|
19721
|
+
OR r.cache_read_tokens <> truth.cache_read_tokens
|
|
19722
|
+
OR r.cache_write_tokens <> truth.cache_write_tokens
|
|
19723
|
+
OR ABS(r.cost_usd - truth.cost_usd) > 1e-9 + ABS(truth.cost_usd) * 1e-9
|
|
19724
|
+
) AS mismatched,
|
|
19725
|
+
(SELECT COUNT(*)
|
|
19726
|
+
FROM usage_rollup r
|
|
19727
|
+
LEFT JOIN truth ON r.api_key_id = truth.k AND r.hour = truth.h
|
|
19728
|
+
WHERE truth.k IS NULL
|
|
19729
|
+
) AS orphans`).get();
|
|
19730
|
+
const buckets = row?.buckets ?? 0;
|
|
19731
|
+
const mismatched = (row?.mismatched ?? 0) + (row?.orphans ?? 0);
|
|
19732
|
+
return { buckets, mismatched, ok: mismatched === 0 };
|
|
19733
|
+
}
|
|
19734
|
+
function backfillRtkUsage(db, dayOffsetMinutes) {
|
|
19735
|
+
const groups = new Map;
|
|
19736
|
+
for (const row of db.query(`SELECT at, api_key_id, requested_model, resolved_provider, resolved_model, credential_id,
|
|
19737
|
+
rtk_applied, rtk_estimated_tokens_saved
|
|
19738
|
+
FROM request_logs
|
|
19739
|
+
WHERE state = 'done'`).all()) {
|
|
19740
|
+
const key = [
|
|
19741
|
+
startOfDay(row.at, dayOffsetMinutes),
|
|
19742
|
+
row.resolved_provider ?? "",
|
|
19743
|
+
row.credential_id ?? "",
|
|
19744
|
+
row.requested_model,
|
|
19745
|
+
row.resolved_model ?? "",
|
|
19746
|
+
row.api_key_id ?? ""
|
|
19747
|
+
];
|
|
19748
|
+
const id = key.join("\x00");
|
|
19749
|
+
const group = groups.get(id) ?? { key, saved: 0, applied: 0 };
|
|
19750
|
+
group.saved += row.rtk_estimated_tokens_saved;
|
|
19751
|
+
group.applied += row.rtk_applied === 1 ? 1 : 0;
|
|
19752
|
+
groups.set(id, group);
|
|
19753
|
+
}
|
|
19754
|
+
for (const { key, saved, applied } of groups.values()) {
|
|
19755
|
+
db.run(`UPDATE usage_daily
|
|
19756
|
+
SET rtk_saved_tokens = ?, rtk_applied_requests = ?
|
|
19757
|
+
WHERE day = ? AND provider = ? AND credential_id = ? AND requested_model = ?
|
|
19758
|
+
AND resolved_model = ? AND api_key_id = ?`, [saved, applied, ...key]);
|
|
19759
|
+
}
|
|
19760
|
+
}
|
|
19761
|
+
|
|
19507
19762
|
// packages/store/src/postgres/db.ts
|
|
19508
19763
|
var {SQL } = globalThis.Bun;
|
|
19509
19764
|
|
|
@@ -19809,11 +20064,74 @@ CREATE TRIGGER api_keys_config_version
|
|
|
19809
20064
|
FOR EACH STATEMENT EXECUTE FUNCTION bump_config_version();
|
|
19810
20065
|
`;
|
|
19811
20066
|
|
|
20067
|
+
// packages/store/src/postgres/migrations/002_health_measurements.sql
|
|
20068
|
+
var _002_health_measurements_default = `-- DEPLOY HAZARD \u2014 read before applying to a fleet.
|
|
20069
|
+
--
|
|
20070
|
+
-- This migration drops two columns that every gateway before it writes on
|
|
20071
|
+
-- the success path (\`UPSERT_HEALTH\` names \`ewma_ttft_ms\` and \`last_used_at\`).
|
|
20072
|
+
-- The first new replica to boot applies it under the migration lock; from that
|
|
20073
|
+
-- moment every OLD replica still running throws on every successful request:
|
|
20074
|
+
-- the upstream call has completed and been billed, the client has received its
|
|
20075
|
+
-- content frames, and then the connection aborts with no terminal frame. The
|
|
20076
|
+
-- \`RollingUpdate\` in \`k8s/deployment.yaml\` does not protect against this \u2014 the
|
|
20077
|
+
-- "surviving" replica is the one that breaks.
|
|
20078
|
+
--
|
|
20079
|
+
-- Required: scale to ONE replica before deploying this release, or accept that
|
|
20080
|
+
-- every old replica fails every successful request until it is replaced.
|
|
20081
|
+
-- Rollback is unsafe: an old image cannot re-add the columns, and running it
|
|
20082
|
+
-- against this schema fails the same way. The only way back is a snapshot
|
|
20083
|
+
-- restore taken before the deploy. Deliberate: kept as a drop, not
|
|
20084
|
+
-- expand/contract, on the operator's decision \u2014 see the write-path spec.
|
|
20085
|
+
--
|
|
20086
|
+
-- \`credential_health\` keeps decisions, not measurements.
|
|
20087
|
+
--
|
|
20088
|
+
-- \`ewma_ttft_ms\` and \`last_used_at\` were written on every successful request,
|
|
20089
|
+
-- and a success that changes nothing else now writes nothing. Both live in the
|
|
20090
|
+
-- gateway's process-local \`loadRegistry\` instead. The console's "last used"
|
|
20091
|
+
-- reads \`request_logs\` through \`usage.lastUsedByCredential\`, served by
|
|
20092
|
+
-- \`idx_request_logs_cred\`.
|
|
20093
|
+
ALTER TABLE credential_health DROP COLUMN ewma_ttft_ms;
|
|
20094
|
+
ALTER TABLE credential_health DROP COLUMN last_used_at;
|
|
20095
|
+
|
|
20096
|
+
-- The statement-level trigger bumped \`config_version\` \u2014 one row, locked until
|
|
20097
|
+
-- commit, by every writer on every replica \u2014 on every health write, and every
|
|
20098
|
+
-- bump makes every replica rebuild its routing snapshot. Only a change to a
|
|
20099
|
+
-- column routing *decides* on needs that; a bare \`consecutive_failures\`
|
|
20100
|
+
-- increment is patched into the held snapshot from the \`healthSaved\` change.
|
|
20101
|
+
--
|
|
20102
|
+
-- The INSERT arm must stay \`FOR EACH ROW\`. The write is an upsert, and a
|
|
20103
|
+
-- statement-level INSERT trigger fires on an upsert whichever path each row
|
|
20104
|
+
-- took, which would bump on every write and make the WHEN clause below moot.
|
|
20105
|
+
-- Row-level \`AFTER INSERT\` fires only for rows actually inserted.
|
|
20106
|
+
DROP TRIGGER credential_health_config_version ON credential_health;
|
|
20107
|
+
|
|
20108
|
+
CREATE TRIGGER credential_health_config_version_upd
|
|
20109
|
+
AFTER UPDATE ON credential_health
|
|
20110
|
+
FOR EACH ROW
|
|
20111
|
+
WHEN (OLD.breaker_state IS DISTINCT FROM NEW.breaker_state
|
|
20112
|
+
OR OLD.rate_limited_until IS DISTINCT FROM NEW.rate_limited_until
|
|
20113
|
+
OR OLD.opened_at IS DISTINCT FROM NEW.opened_at)
|
|
20114
|
+
EXECUTE FUNCTION bump_config_version();
|
|
20115
|
+
|
|
20116
|
+
CREATE TRIGGER credential_health_config_version_ins
|
|
20117
|
+
AFTER INSERT ON credential_health
|
|
20118
|
+
FOR EACH ROW EXECUTE FUNCTION bump_config_version();
|
|
20119
|
+
|
|
20120
|
+
-- DELETE needs no WHEN, so it stays statement-level as in 001: a credential
|
|
20121
|
+
-- with M model rows bumps once, not M times.
|
|
20122
|
+
CREATE TRIGGER credential_health_config_version_del
|
|
20123
|
+
AFTER DELETE ON credential_health
|
|
20124
|
+
FOR EACH STATEMENT EXECUTE FUNCTION bump_config_version();
|
|
20125
|
+
`;
|
|
20126
|
+
|
|
19812
20127
|
// packages/store/src/postgres/db.ts
|
|
19813
20128
|
var num = (v) => Number(v);
|
|
19814
20129
|
var numOrNull = (v) => v === null ? null : Number(v);
|
|
19815
20130
|
var MIGRATION_LOCK = 7140641;
|
|
19816
|
-
var MIGRATIONS = [
|
|
20131
|
+
var MIGRATIONS = [
|
|
20132
|
+
{ id: 1, sql: _001_init_default },
|
|
20133
|
+
{ id: 2, sql: _002_health_measurements_default }
|
|
20134
|
+
];
|
|
19817
20135
|
async function openPg(url2) {
|
|
19818
20136
|
const sql = new SQL({ url: url2 });
|
|
19819
20137
|
const conn = await sql.reserve();
|
|
@@ -20137,31 +20455,25 @@ function toHealth(r) {
|
|
|
20137
20455
|
breakerState: r.breaker_state,
|
|
20138
20456
|
consecutiveFailures: r.consecutive_failures,
|
|
20139
20457
|
openedAt: numOrNull(r.opened_at),
|
|
20140
|
-
rateLimitedUntil: numOrNull(r.rate_limited_until)
|
|
20141
|
-
ewmaTtftMs: r.ewma_ttft_ms,
|
|
20142
|
-
lastUsedAt: numOrNull(r.last_used_at)
|
|
20458
|
+
rateLimitedUntil: numOrNull(r.rate_limited_until)
|
|
20143
20459
|
};
|
|
20144
20460
|
}
|
|
20145
20461
|
var UPSERT_HEALTH = `INSERT INTO credential_health
|
|
20146
20462
|
(credential_id, model, breaker_state, consecutive_failures, opened_at,
|
|
20147
|
-
rate_limited_until
|
|
20148
|
-
VALUES ($1,$2,$3,$4,$5,$6
|
|
20463
|
+
rate_limited_until)
|
|
20464
|
+
VALUES ($1,$2,$3,$4,$5,$6)
|
|
20149
20465
|
ON CONFLICT (credential_id, model) DO UPDATE SET
|
|
20150
20466
|
breaker_state = EXCLUDED.breaker_state,
|
|
20151
20467
|
consecutive_failures = EXCLUDED.consecutive_failures,
|
|
20152
20468
|
opened_at = EXCLUDED.opened_at,
|
|
20153
|
-
rate_limited_until = EXCLUDED.rate_limited_until
|
|
20154
|
-
ewma_ttft_ms = EXCLUDED.ewma_ttft_ms,
|
|
20155
|
-
last_used_at = EXCLUDED.last_used_at`;
|
|
20469
|
+
rate_limited_until = EXCLUDED.rate_limited_until`;
|
|
20156
20470
|
var healthValues = (r) => [
|
|
20157
20471
|
r.credentialId,
|
|
20158
20472
|
r.model,
|
|
20159
20473
|
r.breakerState,
|
|
20160
20474
|
r.consecutiveFailures,
|
|
20161
20475
|
r.openedAt,
|
|
20162
|
-
r.rateLimitedUntil
|
|
20163
|
-
r.ewmaTtftMs,
|
|
20164
|
-
r.lastUsedAt
|
|
20476
|
+
r.rateLimitedUntil
|
|
20165
20477
|
];
|
|
20166
20478
|
function createCredentialRepo(sql, key, emit2) {
|
|
20167
20479
|
const open2 = async (v) => v === null ? null : decrypt(key, v);
|
|
@@ -20354,13 +20666,6 @@ function createCredentialRepo(sql, key, emit2) {
|
|
|
20354
20666
|
const rows = await sql.unsafe("SELECT * FROM credential_health");
|
|
20355
20667
|
return rows.map(toHealth);
|
|
20356
20668
|
},
|
|
20357
|
-
async saveHealth(rows) {
|
|
20358
|
-
await sql.begin(async (tx) => {
|
|
20359
|
-
for (const r of rows)
|
|
20360
|
-
await tx.unsafe(UPSERT_HEALTH, healthValues(r));
|
|
20361
|
-
});
|
|
20362
|
-
emit2({ type: "healthSaved", rows });
|
|
20363
|
-
},
|
|
20364
20669
|
async updateHealth(credentialId, model, apply) {
|
|
20365
20670
|
const written = await sql.begin(async (tx) => {
|
|
20366
20671
|
await tx.unsafe("SELECT pg_advisory_xact_lock(hashtext($1))", [`${credentialId}|${model}`]);
|
|
@@ -20860,229 +21165,6 @@ function isRtkFilterId(value) {
|
|
|
20860
21165
|
return FILTER_IDS.has(value);
|
|
20861
21166
|
}
|
|
20862
21167
|
|
|
20863
|
-
// packages/store/src/sqlite/rollup.ts
|
|
20864
|
-
var HOUR_MS = 3600000;
|
|
20865
|
-
function hourOf(at) {
|
|
20866
|
-
return Math.floor(at / HOUR_MS);
|
|
20867
|
-
}
|
|
20868
|
-
function startOfLocalDay(at) {
|
|
20869
|
-
const day = new Date(at);
|
|
20870
|
-
day.setHours(0, 0, 0, 0);
|
|
20871
|
-
return day.getTime();
|
|
20872
|
-
}
|
|
20873
|
-
var UPSERT = `
|
|
20874
|
-
INSERT INTO usage_daily
|
|
20875
|
-
(day, provider, credential_id, requested_model, resolved_model, api_key_id,
|
|
20876
|
-
requests, errors, input_tokens, output_tokens, cache_read_tokens,
|
|
20877
|
-
cache_write_tokens, rtk_saved_tokens, rtk_applied_requests, cost_usd, duration_ms_sum)
|
|
20878
|
-
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
20879
|
-
ON CONFLICT (day, provider, credential_id, requested_model, resolved_model, api_key_id)
|
|
20880
|
-
DO UPDATE SET
|
|
20881
|
-
requests = requests + excluded.requests,
|
|
20882
|
-
errors = errors + excluded.errors,
|
|
20883
|
-
input_tokens = input_tokens + excluded.input_tokens,
|
|
20884
|
-
output_tokens = output_tokens + excluded.output_tokens,
|
|
20885
|
-
cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
|
|
20886
|
-
cache_write_tokens = cache_write_tokens + excluded.cache_write_tokens,
|
|
20887
|
-
rtk_saved_tokens = rtk_saved_tokens + excluded.rtk_saved_tokens,
|
|
20888
|
-
rtk_applied_requests = rtk_applied_requests + excluded.rtk_applied_requests,
|
|
20889
|
-
cost_usd = cost_usd + excluded.cost_usd,
|
|
20890
|
-
duration_ms_sum = duration_ms_sum + excluded.duration_ms_sum`;
|
|
20891
|
-
function keyOf(log) {
|
|
20892
|
-
return [
|
|
20893
|
-
startOfLocalDay(log.at),
|
|
20894
|
-
log.resolvedProvider ?? "",
|
|
20895
|
-
log.credentialId ?? "",
|
|
20896
|
-
log.requestedModel,
|
|
20897
|
-
log.resolvedModel ?? "",
|
|
20898
|
-
log.apiKeyId ?? ""
|
|
20899
|
-
];
|
|
20900
|
-
}
|
|
20901
|
-
function countersOf(log) {
|
|
20902
|
-
return {
|
|
20903
|
-
requests: 1,
|
|
20904
|
-
errors: log.status >= 400 ? 1 : 0,
|
|
20905
|
-
inputTokens: log.inputTokens,
|
|
20906
|
-
outputTokens: log.outputTokens,
|
|
20907
|
-
cacheReadTokens: log.cacheReadTokens,
|
|
20908
|
-
cacheWriteTokens: log.cacheWriteTokens,
|
|
20909
|
-
rtkSavedTokens: log.rtkEstimatedTokensSaved,
|
|
20910
|
-
rtkAppliedRequests: log.rtkApplied ? 1 : 0,
|
|
20911
|
-
costUsd: log.costUsd,
|
|
20912
|
-
durationMsSum: log.durationMs
|
|
20913
|
-
};
|
|
20914
|
-
}
|
|
20915
|
-
function upsert(db, key, c) {
|
|
20916
|
-
db.run(UPSERT, [
|
|
20917
|
-
...key,
|
|
20918
|
-
c.requests,
|
|
20919
|
-
c.errors,
|
|
20920
|
-
c.inputTokens,
|
|
20921
|
-
c.outputTokens,
|
|
20922
|
-
c.cacheReadTokens,
|
|
20923
|
-
c.cacheWriteTokens,
|
|
20924
|
-
c.rtkSavedTokens,
|
|
20925
|
-
c.rtkAppliedRequests,
|
|
20926
|
-
c.costUsd,
|
|
20927
|
-
c.durationMsSum
|
|
20928
|
-
]);
|
|
20929
|
-
}
|
|
20930
|
-
function rollupLog(db, log) {
|
|
20931
|
-
upsert(db, keyOf(log), countersOf(log));
|
|
20932
|
-
}
|
|
20933
|
-
function backfillDaily(db) {
|
|
20934
|
-
const groups = new Map;
|
|
20935
|
-
for (const row of db.query(`SELECT at, api_key_id, requested_model, resolved_provider, resolved_model, credential_id,
|
|
20936
|
-
status, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
20937
|
-
duration_ms, cost_usd
|
|
20938
|
-
FROM request_logs`).all()) {
|
|
20939
|
-
const key = [
|
|
20940
|
-
startOfLocalDay(row.at),
|
|
20941
|
-
row.resolved_provider ?? "",
|
|
20942
|
-
row.credential_id ?? "",
|
|
20943
|
-
row.requested_model,
|
|
20944
|
-
row.resolved_model ?? "",
|
|
20945
|
-
row.api_key_id ?? ""
|
|
20946
|
-
];
|
|
20947
|
-
const id = key.join("\x00");
|
|
20948
|
-
const seen = groups.get(id);
|
|
20949
|
-
const counters = seen?.counters ?? {
|
|
20950
|
-
requests: 0,
|
|
20951
|
-
errors: 0,
|
|
20952
|
-
inputTokens: 0,
|
|
20953
|
-
outputTokens: 0,
|
|
20954
|
-
cacheReadTokens: 0,
|
|
20955
|
-
cacheWriteTokens: 0,
|
|
20956
|
-
rtkSavedTokens: 0,
|
|
20957
|
-
rtkAppliedRequests: 0,
|
|
20958
|
-
costUsd: 0,
|
|
20959
|
-
durationMsSum: 0
|
|
20960
|
-
};
|
|
20961
|
-
counters.requests += 1;
|
|
20962
|
-
counters.errors += row.status >= 400 ? 1 : 0;
|
|
20963
|
-
counters.inputTokens += row.input_tokens;
|
|
20964
|
-
counters.outputTokens += row.output_tokens;
|
|
20965
|
-
counters.cacheReadTokens += row.cache_read_tokens;
|
|
20966
|
-
counters.cacheWriteTokens += row.cache_write_tokens;
|
|
20967
|
-
counters.costUsd += row.cost_usd;
|
|
20968
|
-
counters.durationMsSum += row.duration_ms;
|
|
20969
|
-
if (seen === undefined)
|
|
20970
|
-
groups.set(id, { key, counters });
|
|
20971
|
-
}
|
|
20972
|
-
for (const group of groups.values())
|
|
20973
|
-
upsert(db, group.key, group.counters);
|
|
20974
|
-
return groups.size;
|
|
20975
|
-
}
|
|
20976
|
-
var HOURLY_UPSERT = `
|
|
20977
|
-
INSERT INTO usage_rollup
|
|
20978
|
-
(api_key_id, hour, requests, input_tokens, output_tokens,
|
|
20979
|
-
cache_read_tokens, cache_write_tokens, cost_usd)
|
|
20980
|
-
VALUES (?,?,?,?,?,?,?,?)
|
|
20981
|
-
ON CONFLICT (api_key_id, hour)
|
|
20982
|
-
DO UPDATE SET
|
|
20983
|
-
requests = requests + excluded.requests,
|
|
20984
|
-
input_tokens = input_tokens + excluded.input_tokens,
|
|
20985
|
-
output_tokens = output_tokens + excluded.output_tokens,
|
|
20986
|
-
cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
|
|
20987
|
-
cache_write_tokens = cache_write_tokens + excluded.cache_write_tokens,
|
|
20988
|
-
cost_usd = cost_usd + excluded.cost_usd`;
|
|
20989
|
-
function rollupHour(db, log) {
|
|
20990
|
-
const apiKeyId = log.apiKeyId;
|
|
20991
|
-
if (apiKeyId === null)
|
|
20992
|
-
return;
|
|
20993
|
-
db.run(HOURLY_UPSERT, [
|
|
20994
|
-
apiKeyId,
|
|
20995
|
-
hourOf(log.at),
|
|
20996
|
-
1,
|
|
20997
|
-
log.inputTokens,
|
|
20998
|
-
log.outputTokens,
|
|
20999
|
-
log.cacheReadTokens,
|
|
21000
|
-
log.cacheWriteTokens,
|
|
21001
|
-
log.costUsd
|
|
21002
|
-
]);
|
|
21003
|
-
}
|
|
21004
|
-
var REBUILD = `
|
|
21005
|
-
INSERT INTO usage_rollup
|
|
21006
|
-
(api_key_id, hour, requests, input_tokens, output_tokens,
|
|
21007
|
-
cache_read_tokens, cache_write_tokens, cost_usd)
|
|
21008
|
-
SELECT api_key_id,
|
|
21009
|
-
CAST(at / 3600000 AS INTEGER),
|
|
21010
|
-
COUNT(*),
|
|
21011
|
-
COALESCE(SUM(input_tokens), 0),
|
|
21012
|
-
COALESCE(SUM(output_tokens), 0),
|
|
21013
|
-
COALESCE(SUM(cache_read_tokens), 0),
|
|
21014
|
-
COALESCE(SUM(cache_write_tokens), 0),
|
|
21015
|
-
COALESCE(SUM(cost_usd), 0)
|
|
21016
|
-
FROM request_logs
|
|
21017
|
-
WHERE state = 'done' AND api_key_id IS NOT NULL
|
|
21018
|
-
GROUP BY api_key_id, CAST(at / 3600000 AS INTEGER)`;
|
|
21019
|
-
function rebuildRollup(db) {
|
|
21020
|
-
db.run("DELETE FROM usage_rollup");
|
|
21021
|
-
db.run(REBUILD);
|
|
21022
|
-
}
|
|
21023
|
-
function auditRollup(db) {
|
|
21024
|
-
const row = db.query(`WITH truth AS (
|
|
21025
|
-
SELECT api_key_id AS k,
|
|
21026
|
-
CAST(at / 3600000 AS INTEGER) AS h,
|
|
21027
|
-
COUNT(*) AS requests,
|
|
21028
|
-
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
21029
|
-
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
21030
|
-
COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,
|
|
21031
|
-
COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,
|
|
21032
|
-
COALESCE(SUM(cost_usd), 0) AS cost_usd
|
|
21033
|
-
FROM request_logs
|
|
21034
|
-
WHERE state = 'done' AND api_key_id IS NOT NULL
|
|
21035
|
-
GROUP BY k, h
|
|
21036
|
-
)
|
|
21037
|
-
SELECT (SELECT COUNT(*) FROM truth) AS buckets,
|
|
21038
|
-
(SELECT COUNT(*)
|
|
21039
|
-
FROM truth
|
|
21040
|
-
LEFT JOIN usage_rollup r ON r.api_key_id = truth.k AND r.hour = truth.h
|
|
21041
|
-
WHERE r.api_key_id IS NULL
|
|
21042
|
-
OR r.requests <> truth.requests
|
|
21043
|
-
OR r.input_tokens <> truth.input_tokens
|
|
21044
|
-
OR r.output_tokens <> truth.output_tokens
|
|
21045
|
-
OR r.cache_read_tokens <> truth.cache_read_tokens
|
|
21046
|
-
OR r.cache_write_tokens <> truth.cache_write_tokens
|
|
21047
|
-
OR ABS(r.cost_usd - truth.cost_usd) > 1e-9 + ABS(truth.cost_usd) * 1e-9
|
|
21048
|
-
) AS mismatched,
|
|
21049
|
-
(SELECT COUNT(*)
|
|
21050
|
-
FROM usage_rollup r
|
|
21051
|
-
LEFT JOIN truth ON r.api_key_id = truth.k AND r.hour = truth.h
|
|
21052
|
-
WHERE truth.k IS NULL
|
|
21053
|
-
) AS orphans`).get();
|
|
21054
|
-
const buckets = row?.buckets ?? 0;
|
|
21055
|
-
const mismatched = (row?.mismatched ?? 0) + (row?.orphans ?? 0);
|
|
21056
|
-
return { buckets, mismatched, ok: mismatched === 0 };
|
|
21057
|
-
}
|
|
21058
|
-
function backfillRtkUsage(db) {
|
|
21059
|
-
const groups = new Map;
|
|
21060
|
-
for (const row of db.query(`SELECT at, api_key_id, requested_model, resolved_provider, resolved_model, credential_id,
|
|
21061
|
-
rtk_applied, rtk_estimated_tokens_saved
|
|
21062
|
-
FROM request_logs
|
|
21063
|
-
WHERE state = 'done'`).all()) {
|
|
21064
|
-
const key = [
|
|
21065
|
-
startOfLocalDay(row.at),
|
|
21066
|
-
row.resolved_provider ?? "",
|
|
21067
|
-
row.credential_id ?? "",
|
|
21068
|
-
row.requested_model,
|
|
21069
|
-
row.resolved_model ?? "",
|
|
21070
|
-
row.api_key_id ?? ""
|
|
21071
|
-
];
|
|
21072
|
-
const id = key.join("\x00");
|
|
21073
|
-
const group = groups.get(id) ?? { key, saved: 0, applied: 0 };
|
|
21074
|
-
group.saved += row.rtk_estimated_tokens_saved;
|
|
21075
|
-
group.applied += row.rtk_applied === 1 ? 1 : 0;
|
|
21076
|
-
groups.set(id, group);
|
|
21077
|
-
}
|
|
21078
|
-
for (const { key, saved, applied } of groups.values()) {
|
|
21079
|
-
db.run(`UPDATE usage_daily
|
|
21080
|
-
SET rtk_saved_tokens = ?, rtk_applied_requests = ?
|
|
21081
|
-
WHERE day = ? AND provider = ? AND credential_id = ? AND requested_model = ?
|
|
21082
|
-
AND resolved_model = ? AND api_key_id = ?`, [saved, applied, ...key]);
|
|
21083
|
-
}
|
|
21084
|
-
}
|
|
21085
|
-
|
|
21086
21168
|
// packages/store/src/postgres/rollup.ts
|
|
21087
21169
|
var DAILY_UPSERT = `
|
|
21088
21170
|
INSERT INTO usage_daily
|
|
@@ -21102,9 +21184,9 @@ var DAILY_UPSERT = `
|
|
|
21102
21184
|
rtk_applied_requests = usage_daily.rtk_applied_requests + EXCLUDED.rtk_applied_requests,
|
|
21103
21185
|
cost_usd = usage_daily.cost_usd + EXCLUDED.cost_usd,
|
|
21104
21186
|
duration_ms_sum = usage_daily.duration_ms_sum + EXCLUDED.duration_ms_sum`;
|
|
21105
|
-
async function rollupLog2(conn, log) {
|
|
21187
|
+
async function rollupLog2(conn, log, dayOffsetMinutes) {
|
|
21106
21188
|
await conn.unsafe(DAILY_UPSERT, [
|
|
21107
|
-
|
|
21189
|
+
startOfDay(log.at, dayOffsetMinutes),
|
|
21108
21190
|
log.resolvedProvider ?? "",
|
|
21109
21191
|
log.credentialId ?? "",
|
|
21110
21192
|
log.requestedModel,
|
|
@@ -21348,7 +21430,7 @@ var sum = (row) => ({
|
|
|
21348
21430
|
tokens: row === undefined ? 0 : num(row.tokens),
|
|
21349
21431
|
costUsd: row === undefined ? 0 : row.cost_usd
|
|
21350
21432
|
});
|
|
21351
|
-
function createUsageRepo(sql, nodeId) {
|
|
21433
|
+
function createUsageRepo(sql, nodeId, dayOffsetMinutes) {
|
|
21352
21434
|
const complete = async (conn, log) => {
|
|
21353
21435
|
const written = (await conn.unsafe(COMPLETE, values(log, "done", nodeId))).count;
|
|
21354
21436
|
if (written === 0)
|
|
@@ -21356,7 +21438,7 @@ function createUsageRepo(sql, nodeId) {
|
|
|
21356
21438
|
const stored = (await conn.unsafe("SELECT * FROM request_logs WHERE id = $1", [log.id]))[0];
|
|
21357
21439
|
if (stored !== undefined) {
|
|
21358
21440
|
const restored = toLog(stored);
|
|
21359
|
-
await rollupLog2(conn, restored);
|
|
21441
|
+
await rollupLog2(conn, restored, dayOffsetMinutes);
|
|
21360
21442
|
await rollupHour2(conn, restored);
|
|
21361
21443
|
}
|
|
21362
21444
|
};
|
|
@@ -21387,6 +21469,10 @@ function createUsageRepo(sql, nodeId) {
|
|
|
21387
21469
|
]) : await sql.unsafe("SELECT * FROM request_logs WHERE api_key_id = $1 ORDER BY at DESC LIMIT $2", [apiKeyId, limit]);
|
|
21388
21470
|
return rows.map(toLog);
|
|
21389
21471
|
},
|
|
21472
|
+
async lastUsedByCredential(credentialId) {
|
|
21473
|
+
const rows = await sql.unsafe("SELECT MAX(at) AS at FROM request_logs WHERE credential_id = $1", [credentialId]);
|
|
21474
|
+
return numOrNull(rows[0]?.at ?? null);
|
|
21475
|
+
},
|
|
21390
21476
|
async scan(cursor, limit) {
|
|
21391
21477
|
const rows = cursor === null ? await sql.unsafe("SELECT * FROM request_logs ORDER BY at, id LIMIT $1", [
|
|
21392
21478
|
limit
|
|
@@ -21449,7 +21535,7 @@ function createUsageRepo(sql, nodeId) {
|
|
|
21449
21535
|
const daily = grain === "daily";
|
|
21450
21536
|
const key = columnFor(grain, q.groupBy);
|
|
21451
21537
|
const split = q.splitBy === undefined ? null : columnFor(grain, q.splitBy);
|
|
21452
|
-
const since = daily ?
|
|
21538
|
+
const since = daily ? startOfDay(q.since, dayOffsetMinutes) : q.since;
|
|
21453
21539
|
const until = q.until ?? Number.MAX_SAFE_INTEGER;
|
|
21454
21540
|
const timeColumn = daily ? "day" : "at";
|
|
21455
21541
|
const scope = q.apiKeyId;
|
|
@@ -21525,6 +21611,7 @@ function redacted(url2) {
|
|
|
21525
21611
|
async function createPostgresStore(opts) {
|
|
21526
21612
|
const logger2 = opts.logger ?? noopLogger;
|
|
21527
21613
|
const nodeId = opts.nodeId ?? crypto.randomUUID();
|
|
21614
|
+
const dayOffsetMinutes = opts.dayOffsetMinutes ?? hostDayOffsetMinutes();
|
|
21528
21615
|
const listeners = new Set;
|
|
21529
21616
|
const emit2 = (change) => {
|
|
21530
21617
|
for (const listener of listeners) {
|
|
@@ -21556,7 +21643,7 @@ async function createPostgresStore(opts) {
|
|
|
21556
21643
|
credentials: createCredentialRepo(sql, opts.encryptionKey, emit2),
|
|
21557
21644
|
config: createConfigRepo(sql, emit2),
|
|
21558
21645
|
keys: createKeyRepo(sql, logger2),
|
|
21559
|
-
usage: createUsageRepo(sql, nodeId),
|
|
21646
|
+
usage: createUsageRepo(sql, nodeId, dayOffsetMinutes),
|
|
21560
21647
|
bodies: createBodyRepo(sql, opts.encryptionKey),
|
|
21561
21648
|
maintenance: createMaintenanceRepo(sql, nodeId),
|
|
21562
21649
|
plugins: createPluginRepo(sql),
|
|
@@ -21793,22 +21880,18 @@ function toHealth2(r) {
|
|
|
21793
21880
|
breakerState: r.breaker_state,
|
|
21794
21881
|
consecutiveFailures: r.consecutive_failures,
|
|
21795
21882
|
openedAt: r.opened_at,
|
|
21796
|
-
rateLimitedUntil: r.rate_limited_until
|
|
21797
|
-
ewmaTtftMs: r.ewma_ttft_ms,
|
|
21798
|
-
lastUsedAt: r.last_used_at
|
|
21883
|
+
rateLimitedUntil: r.rate_limited_until
|
|
21799
21884
|
};
|
|
21800
21885
|
}
|
|
21801
21886
|
var UPSERT_HEALTH2 = `INSERT INTO credential_health
|
|
21802
21887
|
(credential_id, model, breaker_state, consecutive_failures, opened_at,
|
|
21803
|
-
rate_limited_until
|
|
21804
|
-
VALUES (
|
|
21888
|
+
rate_limited_until)
|
|
21889
|
+
VALUES (?,?,?,?,?,?)
|
|
21805
21890
|
ON CONFLICT (credential_id, model) DO UPDATE SET
|
|
21806
21891
|
breaker_state = excluded.breaker_state,
|
|
21807
21892
|
consecutive_failures = excluded.consecutive_failures,
|
|
21808
21893
|
opened_at = excluded.opened_at,
|
|
21809
|
-
rate_limited_until = excluded.rate_limited_until
|
|
21810
|
-
ewma_ttft_ms = excluded.ewma_ttft_ms,
|
|
21811
|
-
last_used_at = excluded.last_used_at`;
|
|
21894
|
+
rate_limited_until = excluded.rate_limited_until`;
|
|
21812
21895
|
function createCredentialRepo2(db, key, emit2 = () => {}) {
|
|
21813
21896
|
const open2 = async (v) => v === null ? null : decrypt(key, v);
|
|
21814
21897
|
const secretsFrom = async (row) => ({
|
|
@@ -21991,22 +22074,13 @@ function createCredentialRepo2(db, key, emit2 = () => {}) {
|
|
|
21991
22074
|
async listHealth() {
|
|
21992
22075
|
return db.query("SELECT * FROM credential_health").all().map(toHealth2);
|
|
21993
22076
|
},
|
|
21994
|
-
async saveHealth(rows) {
|
|
21995
|
-
const stmt = db.prepare(UPSERT_HEALTH2);
|
|
21996
|
-
db.transaction(() => {
|
|
21997
|
-
for (const r of rows) {
|
|
21998
|
-
stmt.run(r.credentialId, r.model, r.breakerState, r.consecutiveFailures, r.openedAt, r.rateLimitedUntil, r.ewmaTtftMs, r.lastUsedAt);
|
|
21999
|
-
}
|
|
22000
|
-
})();
|
|
22001
|
-
emit2({ type: "healthSaved", rows });
|
|
22002
|
-
},
|
|
22003
22077
|
async updateHealth(credentialId, model, apply) {
|
|
22004
22078
|
const current = db.prepare("SELECT * FROM credential_health WHERE credential_id = ? AND model = ?");
|
|
22005
22079
|
const stmt = db.prepare(UPSERT_HEALTH2);
|
|
22006
22080
|
const written = db.transaction(() => {
|
|
22007
22081
|
const row = current.get(credentialId, model);
|
|
22008
22082
|
const next = apply(row === null ? null : toHealth2(row));
|
|
22009
|
-
stmt.run(next.credentialId, next.model, next.breakerState, next.consecutiveFailures, next.openedAt, next.rateLimitedUntil
|
|
22083
|
+
stmt.run(next.credentialId, next.model, next.breakerState, next.consecutiveFailures, next.openedAt, next.rateLimitedUntil);
|
|
22010
22084
|
return next;
|
|
22011
22085
|
})();
|
|
22012
22086
|
emit2({ type: "healthSaved", rows: [written] });
|
|
@@ -22569,6 +22643,34 @@ CREATE TABLE nodes (
|
|
|
22569
22643
|
ALTER TABLE credentials ADD COLUMN token_version INTEGER NOT NULL DEFAULT 0;
|
|
22570
22644
|
`;
|
|
22571
22645
|
|
|
22646
|
+
// packages/store/src/sqlite/migrations/013_health_measurements.sql
|
|
22647
|
+
var _013_health_measurements_default = `-- DEPLOY HAZARD \u2014 read before upgrading an install that is still serving.
|
|
22648
|
+
--
|
|
22649
|
+
-- This migration drops two columns that every gateway before it writes on
|
|
22650
|
+
-- the success path (\`UPSERT_HEALTH\` names \`ewma_ttft_ms\` and \`last_used_at\`).
|
|
22651
|
+
-- SQLite is single-process, so the hazard is the rollback, not the rollout: an
|
|
22652
|
+
-- older gateway started against this database fails every successful request
|
|
22653
|
+
-- after the upstream call has completed and been billed \u2014 content frames out,
|
|
22654
|
+
-- then an aborted connection with no terminal frame. Rolling back the binary
|
|
22655
|
+
-- does not roll back the schema. The only way back is a snapshot taken before
|
|
22656
|
+
-- the upgrade. Deliberate: kept as a drop, not expand/contract, on the
|
|
22657
|
+
-- operator's decision \u2014 see the write-path spec.
|
|
22658
|
+
--
|
|
22659
|
+
-- \`credential_health\` keeps decisions, not measurements.
|
|
22660
|
+
--
|
|
22661
|
+
-- \`ewma_ttft_ms\` and \`last_used_at\` were written on every successful request,
|
|
22662
|
+
-- and a success that changes nothing else now writes nothing. Both live in the
|
|
22663
|
+
-- gateway's process-local \`loadRegistry\` instead: latency is a property of
|
|
22664
|
+
-- *this* process's path to the provider, and the round-robin tiebreak reads
|
|
22665
|
+
-- the value the process holds. The console's "last used" reads
|
|
22666
|
+
-- \`request_logs\` through \`usage.lastUsedByCredential\`, served by
|
|
22667
|
+
-- \`idx_request_logs_cred\`.
|
|
22668
|
+
--
|
|
22669
|
+
-- Neither column is indexed or constrained, so \`DROP COLUMN\` is permitted.
|
|
22670
|
+
ALTER TABLE credential_health DROP COLUMN ewma_ttft_ms;
|
|
22671
|
+
ALTER TABLE credential_health DROP COLUMN last_used_at;
|
|
22672
|
+
`;
|
|
22673
|
+
|
|
22572
22674
|
// packages/store/src/sqlite/db.ts
|
|
22573
22675
|
var MIGRATIONS2 = [
|
|
22574
22676
|
{ id: 1, sql: _001_init_default2 },
|
|
@@ -22582,9 +22684,15 @@ var MIGRATIONS2 = [
|
|
|
22582
22684
|
{ id: 9, sql: _009_key_limits_default },
|
|
22583
22685
|
{ id: 10, sql: _010_usage_rollup_default, after: rebuildRollup },
|
|
22584
22686
|
{ id: 11, sql: _011_plugin_migrations_default },
|
|
22585
|
-
{ id: 12, sql: _012_nodes_default }
|
|
22687
|
+
{ id: 12, sql: _012_nodes_default },
|
|
22688
|
+
{ id: 13, sql: _013_health_measurements_default }
|
|
22586
22689
|
];
|
|
22587
|
-
function
|
|
22690
|
+
function statements(sql) {
|
|
22691
|
+
return sql.split(`
|
|
22692
|
+
`).filter((line) => !line.trimStart().startsWith("--")).join(`
|
|
22693
|
+
`).split(/;[ \t]*(?:\n|$)/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
22694
|
+
}
|
|
22695
|
+
function openDb(path, dayOffsetMinutes = hostDayOffsetMinutes()) {
|
|
22588
22696
|
const db = new Database(path, { create: true });
|
|
22589
22697
|
db.run("PRAGMA journal_mode = WAL");
|
|
22590
22698
|
db.run("PRAGMA synchronous = NORMAL");
|
|
@@ -22596,8 +22704,9 @@ function openDb(path) {
|
|
|
22596
22704
|
if (done.has(m.id))
|
|
22597
22705
|
continue;
|
|
22598
22706
|
db.transaction(() => {
|
|
22599
|
-
|
|
22600
|
-
|
|
22707
|
+
for (const statement of statements(m.sql))
|
|
22708
|
+
db.run(statement);
|
|
22709
|
+
m.after?.(db, dayOffsetMinutes);
|
|
22601
22710
|
db.run("INSERT INTO migrations (id, applied_at) VALUES (?, ?)", [m.id, Date.now()]);
|
|
22602
22711
|
})();
|
|
22603
22712
|
}
|
|
@@ -23014,14 +23123,14 @@ var COMPLETE2 = `INSERT INTO request_logs ${COLUMNS2} VALUES ${PLACEHOLDERS2}
|
|
|
23014
23123
|
rtk_estimated_tokens_saved = excluded.rtk_estimated_tokens_saved,
|
|
23015
23124
|
rtk_filters = excluded.rtk_filters
|
|
23016
23125
|
WHERE request_logs.state = 'pending'`;
|
|
23017
|
-
function createUsageRepo2(db, nodeId) {
|
|
23126
|
+
function createUsageRepo2(db, nodeId, dayOffsetMinutes = hostDayOffsetMinutes()) {
|
|
23018
23127
|
const complete = db.transaction((log) => {
|
|
23019
23128
|
if (db.run(COMPLETE2, values2(log, "done", nodeId)).changes === 0)
|
|
23020
23129
|
return;
|
|
23021
23130
|
const stored = db.query("SELECT * FROM request_logs WHERE id = ?").get(log.id);
|
|
23022
23131
|
if (stored !== null) {
|
|
23023
23132
|
const restored = toLog2(stored);
|
|
23024
|
-
rollupLog(db, restored);
|
|
23133
|
+
rollupLog(db, restored, dayOffsetMinutes);
|
|
23025
23134
|
rollupHour(db, restored);
|
|
23026
23135
|
}
|
|
23027
23136
|
});
|
|
@@ -23071,6 +23180,10 @@ function createUsageRepo2(db, nodeId) {
|
|
|
23071
23180
|
}
|
|
23072
23181
|
return db.query("SELECT * FROM request_logs WHERE api_key_id = ? ORDER BY at DESC LIMIT ?").all(apiKeyId, limit).map(toLog2);
|
|
23073
23182
|
},
|
|
23183
|
+
async lastUsedByCredential(credentialId) {
|
|
23184
|
+
const row = db.query("SELECT MAX(at) AS at FROM request_logs WHERE credential_id = ?").get(credentialId);
|
|
23185
|
+
return row?.at ?? null;
|
|
23186
|
+
},
|
|
23074
23187
|
async scan(cursor, limit) {
|
|
23075
23188
|
const rows = cursor === null ? db.query("SELECT * FROM request_logs ORDER BY at, id LIMIT ?").all(limit) : db.query(`SELECT * FROM request_logs
|
|
23076
23189
|
WHERE at > ? OR (at = ? AND id > ?)
|
|
@@ -23132,7 +23245,7 @@ function createUsageRepo2(db, nodeId) {
|
|
|
23132
23245
|
const daily = grain === "daily";
|
|
23133
23246
|
const key = columnFor2(grain, q.groupBy);
|
|
23134
23247
|
const split = q.splitBy === undefined ? null : columnFor2(grain, q.splitBy);
|
|
23135
|
-
const since = daily ?
|
|
23248
|
+
const since = daily ? startOfDay(q.since, dayOffsetMinutes) : q.since;
|
|
23136
23249
|
const until = q.until ?? Number.MAX_SAFE_INTEGER;
|
|
23137
23250
|
const timeColumn = daily ? "day" : "at";
|
|
23138
23251
|
const scope = q.apiKeyId;
|
|
@@ -23183,6 +23296,7 @@ function createUsageRepo2(db, nodeId) {
|
|
|
23183
23296
|
async function createStore(opts) {
|
|
23184
23297
|
const logger2 = opts.logger ?? noopLogger;
|
|
23185
23298
|
const nodeId = opts.nodeId ?? crypto.randomUUID();
|
|
23299
|
+
const dayOffsetMinutes = opts.dayOffsetMinutes ?? hostDayOffsetMinutes();
|
|
23186
23300
|
const listeners = new Set;
|
|
23187
23301
|
const emit2 = (change) => {
|
|
23188
23302
|
for (const listener of listeners) {
|
|
@@ -23192,14 +23306,14 @@ async function createStore(opts) {
|
|
|
23192
23306
|
}
|
|
23193
23307
|
};
|
|
23194
23308
|
const open2 = () => {
|
|
23195
|
-
const db = openDb(opts.path);
|
|
23309
|
+
const db = openDb(opts.path, dayOffsetMinutes);
|
|
23196
23310
|
logger2.debug("store opened", { path: opts.path });
|
|
23197
23311
|
return {
|
|
23198
23312
|
db,
|
|
23199
23313
|
credentials: createCredentialRepo2(db, opts.encryptionKey, emit2),
|
|
23200
23314
|
config: createConfigRepo2(db, emit2),
|
|
23201
23315
|
keys: createKeyRepo2(db, logger2),
|
|
23202
|
-
usage: createUsageRepo2(db, nodeId),
|
|
23316
|
+
usage: createUsageRepo2(db, nodeId, dayOffsetMinutes),
|
|
23203
23317
|
bodies: createBodyRepo2(db, opts.encryptionKey, bodiesDirFor(opts.path)),
|
|
23204
23318
|
maintenance: createMaintenanceRepo2(db, nodeId),
|
|
23205
23319
|
plugins: createPluginRepo2(db)
|
|
@@ -23225,7 +23339,6 @@ async function createStore(opts) {
|
|
|
23225
23339
|
updateSecrets: (id, secrets, expiresAt, expectedVersion) => handle.credentials.updateSecrets(id, secrets, expiresAt, expectedVersion),
|
|
23226
23340
|
remove: (id) => handle.credentials.remove(id),
|
|
23227
23341
|
listHealth: () => handle.credentials.listHealth(),
|
|
23228
|
-
saveHealth: (rows) => handle.credentials.saveHealth(rows),
|
|
23229
23342
|
updateHealth: (id, model, apply) => handle.credentials.updateHealth(id, model, apply),
|
|
23230
23343
|
listQuota: () => handle.credentials.listQuota(),
|
|
23231
23344
|
saveQuota: (rows) => handle.credentials.saveQuota(rows),
|
|
@@ -23260,6 +23373,7 @@ async function createStore(opts) {
|
|
|
23260
23373
|
append: (log) => handle.usage.append(log),
|
|
23261
23374
|
sweepPending: (now) => handle.usage.sweepPending(now),
|
|
23262
23375
|
recent: (limit, apiKeyId) => handle.usage.recent(limit, apiKeyId),
|
|
23376
|
+
lastUsedByCredential: (credentialId) => handle.usage.lastUsedByCredential(credentialId),
|
|
23263
23377
|
scan: (cursor, limit) => handle.usage.scan(cursor, limit),
|
|
23264
23378
|
aggregate: (q) => handle.usage.aggregate(q),
|
|
23265
23379
|
sumSince: (apiKeyId, sinceMs) => handle.usage.sumSince(apiKeyId, sinceMs),
|
|
@@ -24138,6 +24252,7 @@ var PROVIDER_IDS = Object.keys(PROVIDER_DESCRIPTORS);
|
|
|
24138
24252
|
var MIN_KEY_LENGTH = 16;
|
|
24139
24253
|
var TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
24140
24254
|
var DECIMAL_INTEGER = /^\d+$/;
|
|
24255
|
+
var SIGNED_INTEGER = /^-?\d+$/;
|
|
24141
24256
|
function optionalText(value, fallback) {
|
|
24142
24257
|
return value?.trim() || fallback;
|
|
24143
24258
|
}
|
|
@@ -24168,6 +24283,11 @@ function loadConfig(env) {
|
|
|
24168
24283
|
} else if (databaseUrl !== undefined || redisUrl !== undefined) {
|
|
24169
24284
|
throw new Error("OMNI_DATABASE_URL and OMNI_REDIS_URL are cluster-mode settings; set OMNI_CLUSTER_MODE=true or unset them");
|
|
24170
24285
|
}
|
|
24286
|
+
const rawDayOffset = env.OMNI_DAY_OFFSET_MINUTES?.trim();
|
|
24287
|
+
const parsedDayOffset = rawDayOffset === undefined ? NaN : Number(rawDayOffset);
|
|
24288
|
+
const validDayOffset = rawDayOffset !== undefined && SIGNED_INTEGER.test(rawDayOffset) && Number.isInteger(parsedDayOffset) && parsedDayOffset >= -1440 && parsedDayOffset <= 1440;
|
|
24289
|
+
const dayOffsetMinutes = validDayOffset ? parsedDayOffset : hostDayOffsetMinutes();
|
|
24290
|
+
const dayOffsetFallbackFrom = rawDayOffset !== undefined && !validDayOffset ? rawDayOffset : null;
|
|
24171
24291
|
const bodyLoggingAllowed = TRUTHY.has((env.OMNI_BODY_LOGGING_ALLOWED ?? "").trim().toLowerCase());
|
|
24172
24292
|
const rawLogLevel = env.OMNI_LOG_LEVEL?.trim();
|
|
24173
24293
|
const logLevel = parseLogLevel(rawLogLevel);
|
|
@@ -24198,6 +24318,8 @@ function loadConfig(env) {
|
|
|
24198
24318
|
clusterMode,
|
|
24199
24319
|
databaseUrl: clusterMode ? databaseUrl ?? null : null,
|
|
24200
24320
|
redisUrl: clusterMode ? redisUrl ?? null : null,
|
|
24321
|
+
dayOffsetMinutes,
|
|
24322
|
+
dayOffsetFallbackFrom,
|
|
24201
24323
|
metricsToken,
|
|
24202
24324
|
metricsMaxSeries,
|
|
24203
24325
|
otlpEndpoint,
|
|
@@ -31185,6 +31307,7 @@ function eligible(input2) {
|
|
|
31185
31307
|
drop("expired");
|
|
31186
31308
|
continue;
|
|
31187
31309
|
}
|
|
31310
|
+
let probe = false;
|
|
31188
31311
|
const h = snapshot.health.get(healthKey(credential.id, target.model));
|
|
31189
31312
|
if (h !== undefined) {
|
|
31190
31313
|
if (h.rateLimitedUntil !== null && h.rateLimitedUntil > now) {
|
|
@@ -31197,6 +31320,9 @@ function eligible(input2) {
|
|
|
31197
31320
|
drop("breaker:open");
|
|
31198
31321
|
continue;
|
|
31199
31322
|
}
|
|
31323
|
+
probe = true;
|
|
31324
|
+
} else if (h.breakerState === "halfOpen") {
|
|
31325
|
+
probe = true;
|
|
31200
31326
|
}
|
|
31201
31327
|
}
|
|
31202
31328
|
const spent = (snapshot.quota.get(credential.id) ?? []).find((w) => w.limit !== null && w.used >= w.limit && (w.resetsAt === null || w.resetsAt > now));
|
|
@@ -31204,7 +31330,7 @@ function eligible(input2) {
|
|
|
31204
31330
|
drop(`quota:${spent.windowType}`);
|
|
31205
31331
|
continue;
|
|
31206
31332
|
}
|
|
31207
|
-
pairs.push({ credential, target });
|
|
31333
|
+
pairs.push({ credential, target, probe });
|
|
31208
31334
|
}
|
|
31209
31335
|
if (target.credentialId === undefined && providerSeen && !servedAny) {
|
|
31210
31336
|
excluded.push({
|
|
@@ -31281,7 +31407,7 @@ function healthScore(h) {
|
|
|
31281
31407
|
return h?.breakerState === "open" || h?.breakerState === "halfOpen" ? base * 0.5 : base;
|
|
31282
31408
|
}
|
|
31283
31409
|
function score(pairs, input2) {
|
|
31284
|
-
const { snapshot, now, load } = input2;
|
|
31410
|
+
const { snapshot, now, load, recent } = input2;
|
|
31285
31411
|
const w = snapshot.settings.weights;
|
|
31286
31412
|
const tiers = pairs.map((p) => p.target.tier);
|
|
31287
31413
|
const minTier = Math.min(...tiers);
|
|
@@ -31289,9 +31415,10 @@ function score(pairs, input2) {
|
|
|
31289
31415
|
const tokens2 = estimateTokens(input2.request);
|
|
31290
31416
|
const costs = pairs.map((p) => requestCost(p.target, tokens2));
|
|
31291
31417
|
const bestCost = bestPositive(costs);
|
|
31418
|
+
const ewmaOf = (key) => recent?.get(key)?.ewmaTtftMs ?? null;
|
|
31292
31419
|
const bestLatency = bestPositive(pairs.flatMap((p) => {
|
|
31293
|
-
const
|
|
31294
|
-
return
|
|
31420
|
+
const ewma = ewmaOf(healthKey(p.credential.id, p.target.model));
|
|
31421
|
+
return ewma === null ? [] : [ewma];
|
|
31295
31422
|
}));
|
|
31296
31423
|
return pairs.map((pair, i) => {
|
|
31297
31424
|
const key = healthKey(pair.credential.id, pair.target.model);
|
|
@@ -31303,12 +31430,14 @@ function score(pairs, input2) {
|
|
|
31303
31430
|
const quota = quotaHeadroom(pair.credential, snapshot.quota.get(pair.credential.id) ?? [], now, snapshot.settings.quotaPollIntervalMs);
|
|
31304
31431
|
const ownCost = costs[i];
|
|
31305
31432
|
const cost = ownCost <= 0 || bestCost === null ? UNKNOWN : ratio(ownCost, bestCost);
|
|
31306
|
-
const
|
|
31433
|
+
const ewma = ewmaOf(key);
|
|
31434
|
+
const latency = ewma === null || ewma <= 0 || bestLatency === null ? UNKNOWN : ratio(ewma, bestLatency);
|
|
31307
31435
|
const reasons = { tier, health, quota, cost, latency, load: loadTerm };
|
|
31308
31436
|
const base = tier * w.tier + health * w.health + quota * w.quota + cost * w.cost + latency * w.latency + loadTerm * w.load;
|
|
31309
31437
|
return {
|
|
31310
31438
|
credential: pair.credential,
|
|
31311
31439
|
target: pair.target,
|
|
31440
|
+
probe: pair.probe,
|
|
31312
31441
|
score: base * pair.credential.weight * pair.target.weight,
|
|
31313
31442
|
reasons
|
|
31314
31443
|
};
|
|
@@ -31348,8 +31477,8 @@ function rank(input2) {
|
|
|
31348
31477
|
break;
|
|
31349
31478
|
case "roundRobin": {
|
|
31350
31479
|
const idle = (c) => {
|
|
31351
|
-
const
|
|
31352
|
-
return
|
|
31480
|
+
const at = input2.recent?.get(healthKey(c.credential.id, c.target.model))?.lastReleasedAt;
|
|
31481
|
+
return at === undefined ? Number.POSITIVE_INFINITY : input2.now - at;
|
|
31353
31482
|
};
|
|
31354
31483
|
const spent = (c) => headroom(c) < QUOTA_FLOOR ? 1 : 0;
|
|
31355
31484
|
scored.sort((a, b) => spent(a) - spent(b) || inflight(a) - inflight(b) || idle(b) - idle(a));
|
|
@@ -31521,16 +31650,23 @@ async function refreshCredential(deps, id) {
|
|
|
31521
31650
|
return getCredential(deps.store, id);
|
|
31522
31651
|
}
|
|
31523
31652
|
async function credentialHealth(deps) {
|
|
31524
|
-
const [health, quota, settings] = await Promise.all([
|
|
31653
|
+
const [health, quota, settings, credentials] = await Promise.all([
|
|
31525
31654
|
deps.store.credentials.listHealth(),
|
|
31526
31655
|
deps.store.credentials.listQuota(),
|
|
31527
|
-
deps.store.config.getSettings()
|
|
31656
|
+
deps.store.config.getSettings(),
|
|
31657
|
+
deps.store.credentials.list()
|
|
31528
31658
|
]);
|
|
31659
|
+
const lastUsed = {};
|
|
31660
|
+
for (const c of credentials) {
|
|
31661
|
+
const at = await deps.store.usage.lastUsedByCredential(c.id);
|
|
31662
|
+
if (at !== null)
|
|
31663
|
+
lastUsed[c.id] = at;
|
|
31664
|
+
}
|
|
31529
31665
|
const burn = burnEstimates(quota, {
|
|
31530
31666
|
now: deps.now(),
|
|
31531
31667
|
pollIntervalMs: settings.quotaPollIntervalMs
|
|
31532
31668
|
});
|
|
31533
|
-
return { health, quota, burn };
|
|
31669
|
+
return { health, quota, burn, lastUsed };
|
|
31534
31670
|
}
|
|
31535
31671
|
async function credentialStatus(store, options) {
|
|
31536
31672
|
const [credentials, quota, adminConfigured] = await Promise.all([
|
|
@@ -31644,14 +31780,14 @@ function listSnapshots(deps) {
|
|
|
31644
31780
|
}
|
|
31645
31781
|
return snapshots.sort((a, b) => b.createdAt - a.createdAt);
|
|
31646
31782
|
}
|
|
31647
|
-
var
|
|
31783
|
+
var DAY_MS2 = 86400000;
|
|
31648
31784
|
var SNAPSHOT_HEADROOM = 1.1;
|
|
31649
31785
|
function retentionOf(settings) {
|
|
31650
31786
|
return { keepLatest: settings.snapshotKeepLatest, maxAgeDays: settings.snapshotMaxAgeDays };
|
|
31651
31787
|
}
|
|
31652
31788
|
function applyRetention(deps, policy, now) {
|
|
31653
31789
|
const dir = snapshotsDir(deps);
|
|
31654
|
-
const cutoff = now - policy.maxAgeDays *
|
|
31790
|
+
const cutoff = now - policy.maxAgeDays * DAY_MS2;
|
|
31655
31791
|
const snapshots = listSnapshots(deps);
|
|
31656
31792
|
const undo = snapshots.find((snapshot) => snapshot.reason === "preRestore")?.id;
|
|
31657
31793
|
let removed = 0;
|
|
@@ -33476,12 +33612,12 @@ function readsNothing(scope) {
|
|
|
33476
33612
|
return scope.kind === "none" || scope.kind === "key" && scope.apiKeyId === "";
|
|
33477
33613
|
}
|
|
33478
33614
|
// packages/control/src/quota/history.ts
|
|
33479
|
-
var
|
|
33615
|
+
var DAY_MS3 = 24 * 60 * 60 * 1000;
|
|
33480
33616
|
var HOUR_MS3 = 3600000;
|
|
33481
33617
|
async function retainedSpan(deps, input2, maxSpanMs) {
|
|
33482
33618
|
const now = deps.now();
|
|
33483
33619
|
const settings = await deps.store.config.getSettings();
|
|
33484
|
-
const retained = now - settings.logRetentionDays *
|
|
33620
|
+
const retained = now - settings.logRetentionDays * DAY_MS3;
|
|
33485
33621
|
const oldest = maxSpanMs === undefined ? retained : Math.max(retained, now - maxSpanMs);
|
|
33486
33622
|
return {
|
|
33487
33623
|
since: Math.max(optionalNumber(input2.since, oldest), oldest),
|
|
@@ -33924,7 +34060,11 @@ function createContext(parsed2, options = {}) {
|
|
|
33924
34060
|
const current = config2;
|
|
33925
34061
|
if (current === null)
|
|
33926
34062
|
throw new CliError(configError ?? "invalid configuration");
|
|
33927
|
-
opened = deriveKey(current.encryptionKey).then((key) => createStore({
|
|
34063
|
+
opened = deriveKey(current.encryptionKey).then((key) => createStore({
|
|
34064
|
+
path: databasePath,
|
|
34065
|
+
encryptionKey: key,
|
|
34066
|
+
dayOffsetMinutes: current.dayOffsetMinutes
|
|
34067
|
+
})).then((created) => {
|
|
33928
34068
|
store = created;
|
|
33929
34069
|
return created;
|
|
33930
34070
|
});
|
|
@@ -34775,17 +34915,17 @@ var credentialsAddKey = {
|
|
|
34775
34915
|
};
|
|
34776
34916
|
var credentialsHealth = {
|
|
34777
34917
|
usage: "credentials health",
|
|
34778
|
-
summary: "Show breaker state
|
|
34918
|
+
summary: "Show breaker state per credential and model",
|
|
34779
34919
|
options: { all: { type: "boolean" } },
|
|
34780
34920
|
async run(args, { ctx, writer }) {
|
|
34781
34921
|
const store = await ctx.store();
|
|
34782
|
-
const [{ health: rows }, credentials] = await Promise.all([
|
|
34922
|
+
const [{ health: rows, lastUsed }, credentials] = await Promise.all([
|
|
34783
34923
|
credentialHealth({ store, now: ctx.now }),
|
|
34784
34924
|
listCredentials(store)
|
|
34785
34925
|
]);
|
|
34786
34926
|
const labels = new Map(credentials.map((c) => [c.id, c.label]));
|
|
34787
34927
|
const shown = boolFlag(args.values, "all") ? rows : rows.filter((r) => r.breakerState !== "closed" || r.consecutiveFailures > 0);
|
|
34788
|
-
emit(ctx, writer, { health: rows }, () => {
|
|
34928
|
+
emit(ctx, writer, { health: rows, lastUsed }, () => {
|
|
34789
34929
|
if (shown.length === 0)
|
|
34790
34930
|
return "every credential is healthy";
|
|
34791
34931
|
return table([
|
|
@@ -34793,15 +34933,13 @@ var credentialsHealth = {
|
|
|
34793
34933
|
{ header: "MODEL" },
|
|
34794
34934
|
{ header: "BREAKER" },
|
|
34795
34935
|
{ header: "FAILS", align: "right" },
|
|
34796
|
-
{ header: "TTFT", align: "right" },
|
|
34797
34936
|
{ header: "LAST USED" }
|
|
34798
34937
|
], shown.map((row) => [
|
|
34799
34938
|
labels.get(row.credentialId) ?? row.credentialId,
|
|
34800
34939
|
row.model,
|
|
34801
34940
|
state(ctx, row.breakerState === "closed", row.breakerState),
|
|
34802
34941
|
String(row.consecutiveFailures),
|
|
34803
|
-
|
|
34804
|
-
paint(ctx, "dim", formatTime(row.lastUsedAt))
|
|
34942
|
+
paint(ctx, "dim", formatTime(lastUsed[row.credentialId] ?? null))
|
|
34805
34943
|
]));
|
|
34806
34944
|
});
|
|
34807
34945
|
}
|
|
@@ -34842,7 +34980,8 @@ var dbMigrate = {
|
|
|
34842
34980
|
const source = await ctx.store();
|
|
34843
34981
|
const target = await createPostgresStore({
|
|
34844
34982
|
url: to,
|
|
34845
|
-
encryptionKey: await deriveKey(ctx.config().encryptionKey)
|
|
34983
|
+
encryptionKey: await deriveKey(ctx.config().encryptionKey),
|
|
34984
|
+
dayOffsetMinutes: ctx.config().dayOffsetMinutes
|
|
34846
34985
|
});
|
|
34847
34986
|
try {
|
|
34848
34987
|
const report = await copyStore(source, target);
|
|
@@ -35755,7 +35894,7 @@ function createServiceDeps(input2) {
|
|
|
35755
35894
|
}
|
|
35756
35895
|
|
|
35757
35896
|
// apps/cli/src/version.ts
|
|
35758
|
-
var VERSION = "0.
|
|
35897
|
+
var VERSION = "0.11.0";
|
|
35759
35898
|
|
|
35760
35899
|
// apps/cli/src/commands/service.ts
|
|
35761
35900
|
function gatewayArgv(root) {
|