omnigateway 0.10.2 → 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 +454 -303
- package/gateway.js +593 -360
- 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,
|
|
@@ -26505,6 +26627,17 @@ function closingTurn(contents, note2) {
|
|
|
26505
26627
|
}
|
|
26506
26628
|
var hasCacheControl = (block) => ("cacheControl" in block) && block.cacheControl !== undefined;
|
|
26507
26629
|
var PRUNED = "antigravity:tool-schema-pruned";
|
|
26630
|
+
var AGENT_PREAMBLE_DROPPED = "antigravity:agent-preamble-dropped";
|
|
26631
|
+
function dropAgentPreamble(system, note2) {
|
|
26632
|
+
const paragraphs = system.split(/(?:\r?\n){2,}/);
|
|
26633
|
+
const kept = paragraphs.filter((p) => p.trim() !== AGENT_PREAMBLE);
|
|
26634
|
+
if (kept.length === paragraphs.length)
|
|
26635
|
+
return system;
|
|
26636
|
+
note2(AGENT_PREAMBLE_DROPPED);
|
|
26637
|
+
return kept.join(`
|
|
26638
|
+
|
|
26639
|
+
`);
|
|
26640
|
+
}
|
|
26508
26641
|
var MAX_STOP_SEQUENCES = 5;
|
|
26509
26642
|
var MAX_THINKING_BUDGET = 65535;
|
|
26510
26643
|
function scalarValue(kind, value) {
|
|
@@ -26797,8 +26930,9 @@ function toAntigravityWire(req, model, identity) {
|
|
|
26797
26930
|
const request2 = {
|
|
26798
26931
|
contents: closingTurn(openingTurn(mergeSameRole(contents), note2), note2)
|
|
26799
26932
|
};
|
|
26800
|
-
const
|
|
26801
|
-
|
|
26933
|
+
const joined = systemText(req.system, "antigravity", note2);
|
|
26934
|
+
const system = joined === undefined ? undefined : dropAgentPreamble(joined, note2);
|
|
26935
|
+
if (system !== undefined && system.trim().length > 0) {
|
|
26802
26936
|
request2.systemInstruction = { parts: [{ text: system }] };
|
|
26803
26937
|
}
|
|
26804
26938
|
const generationConfig = {};
|
|
@@ -31173,6 +31307,7 @@ function eligible(input2) {
|
|
|
31173
31307
|
drop("expired");
|
|
31174
31308
|
continue;
|
|
31175
31309
|
}
|
|
31310
|
+
let probe = false;
|
|
31176
31311
|
const h = snapshot.health.get(healthKey(credential.id, target.model));
|
|
31177
31312
|
if (h !== undefined) {
|
|
31178
31313
|
if (h.rateLimitedUntil !== null && h.rateLimitedUntil > now) {
|
|
@@ -31185,6 +31320,9 @@ function eligible(input2) {
|
|
|
31185
31320
|
drop("breaker:open");
|
|
31186
31321
|
continue;
|
|
31187
31322
|
}
|
|
31323
|
+
probe = true;
|
|
31324
|
+
} else if (h.breakerState === "halfOpen") {
|
|
31325
|
+
probe = true;
|
|
31188
31326
|
}
|
|
31189
31327
|
}
|
|
31190
31328
|
const spent = (snapshot.quota.get(credential.id) ?? []).find((w) => w.limit !== null && w.used >= w.limit && (w.resetsAt === null || w.resetsAt > now));
|
|
@@ -31192,7 +31330,7 @@ function eligible(input2) {
|
|
|
31192
31330
|
drop(`quota:${spent.windowType}`);
|
|
31193
31331
|
continue;
|
|
31194
31332
|
}
|
|
31195
|
-
pairs.push({ credential, target });
|
|
31333
|
+
pairs.push({ credential, target, probe });
|
|
31196
31334
|
}
|
|
31197
31335
|
if (target.credentialId === undefined && providerSeen && !servedAny) {
|
|
31198
31336
|
excluded.push({
|
|
@@ -31269,7 +31407,7 @@ function healthScore(h) {
|
|
|
31269
31407
|
return h?.breakerState === "open" || h?.breakerState === "halfOpen" ? base * 0.5 : base;
|
|
31270
31408
|
}
|
|
31271
31409
|
function score(pairs, input2) {
|
|
31272
|
-
const { snapshot, now, load } = input2;
|
|
31410
|
+
const { snapshot, now, load, recent } = input2;
|
|
31273
31411
|
const w = snapshot.settings.weights;
|
|
31274
31412
|
const tiers = pairs.map((p) => p.target.tier);
|
|
31275
31413
|
const minTier = Math.min(...tiers);
|
|
@@ -31277,9 +31415,10 @@ function score(pairs, input2) {
|
|
|
31277
31415
|
const tokens2 = estimateTokens(input2.request);
|
|
31278
31416
|
const costs = pairs.map((p) => requestCost(p.target, tokens2));
|
|
31279
31417
|
const bestCost = bestPositive(costs);
|
|
31418
|
+
const ewmaOf = (key) => recent?.get(key)?.ewmaTtftMs ?? null;
|
|
31280
31419
|
const bestLatency = bestPositive(pairs.flatMap((p) => {
|
|
31281
|
-
const
|
|
31282
|
-
return
|
|
31420
|
+
const ewma = ewmaOf(healthKey(p.credential.id, p.target.model));
|
|
31421
|
+
return ewma === null ? [] : [ewma];
|
|
31283
31422
|
}));
|
|
31284
31423
|
return pairs.map((pair, i) => {
|
|
31285
31424
|
const key = healthKey(pair.credential.id, pair.target.model);
|
|
@@ -31291,12 +31430,14 @@ function score(pairs, input2) {
|
|
|
31291
31430
|
const quota = quotaHeadroom(pair.credential, snapshot.quota.get(pair.credential.id) ?? [], now, snapshot.settings.quotaPollIntervalMs);
|
|
31292
31431
|
const ownCost = costs[i];
|
|
31293
31432
|
const cost = ownCost <= 0 || bestCost === null ? UNKNOWN : ratio(ownCost, bestCost);
|
|
31294
|
-
const
|
|
31433
|
+
const ewma = ewmaOf(key);
|
|
31434
|
+
const latency = ewma === null || ewma <= 0 || bestLatency === null ? UNKNOWN : ratio(ewma, bestLatency);
|
|
31295
31435
|
const reasons = { tier, health, quota, cost, latency, load: loadTerm };
|
|
31296
31436
|
const base = tier * w.tier + health * w.health + quota * w.quota + cost * w.cost + latency * w.latency + loadTerm * w.load;
|
|
31297
31437
|
return {
|
|
31298
31438
|
credential: pair.credential,
|
|
31299
31439
|
target: pair.target,
|
|
31440
|
+
probe: pair.probe,
|
|
31300
31441
|
score: base * pair.credential.weight * pair.target.weight,
|
|
31301
31442
|
reasons
|
|
31302
31443
|
};
|
|
@@ -31336,8 +31477,8 @@ function rank(input2) {
|
|
|
31336
31477
|
break;
|
|
31337
31478
|
case "roundRobin": {
|
|
31338
31479
|
const idle = (c) => {
|
|
31339
|
-
const
|
|
31340
|
-
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;
|
|
31341
31482
|
};
|
|
31342
31483
|
const spent = (c) => headroom(c) < QUOTA_FLOOR ? 1 : 0;
|
|
31343
31484
|
scored.sort((a, b) => spent(a) - spent(b) || inflight(a) - inflight(b) || idle(b) - idle(a));
|
|
@@ -31509,16 +31650,23 @@ async function refreshCredential(deps, id) {
|
|
|
31509
31650
|
return getCredential(deps.store, id);
|
|
31510
31651
|
}
|
|
31511
31652
|
async function credentialHealth(deps) {
|
|
31512
|
-
const [health, quota, settings] = await Promise.all([
|
|
31653
|
+
const [health, quota, settings, credentials] = await Promise.all([
|
|
31513
31654
|
deps.store.credentials.listHealth(),
|
|
31514
31655
|
deps.store.credentials.listQuota(),
|
|
31515
|
-
deps.store.config.getSettings()
|
|
31656
|
+
deps.store.config.getSettings(),
|
|
31657
|
+
deps.store.credentials.list()
|
|
31516
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
|
+
}
|
|
31517
31665
|
const burn = burnEstimates(quota, {
|
|
31518
31666
|
now: deps.now(),
|
|
31519
31667
|
pollIntervalMs: settings.quotaPollIntervalMs
|
|
31520
31668
|
});
|
|
31521
|
-
return { health, quota, burn };
|
|
31669
|
+
return { health, quota, burn, lastUsed };
|
|
31522
31670
|
}
|
|
31523
31671
|
async function credentialStatus(store, options) {
|
|
31524
31672
|
const [credentials, quota, adminConfigured] = await Promise.all([
|
|
@@ -31632,14 +31780,14 @@ function listSnapshots(deps) {
|
|
|
31632
31780
|
}
|
|
31633
31781
|
return snapshots.sort((a, b) => b.createdAt - a.createdAt);
|
|
31634
31782
|
}
|
|
31635
|
-
var
|
|
31783
|
+
var DAY_MS2 = 86400000;
|
|
31636
31784
|
var SNAPSHOT_HEADROOM = 1.1;
|
|
31637
31785
|
function retentionOf(settings) {
|
|
31638
31786
|
return { keepLatest: settings.snapshotKeepLatest, maxAgeDays: settings.snapshotMaxAgeDays };
|
|
31639
31787
|
}
|
|
31640
31788
|
function applyRetention(deps, policy, now) {
|
|
31641
31789
|
const dir = snapshotsDir(deps);
|
|
31642
|
-
const cutoff = now - policy.maxAgeDays *
|
|
31790
|
+
const cutoff = now - policy.maxAgeDays * DAY_MS2;
|
|
31643
31791
|
const snapshots = listSnapshots(deps);
|
|
31644
31792
|
const undo = snapshots.find((snapshot) => snapshot.reason === "preRestore")?.id;
|
|
31645
31793
|
let removed = 0;
|
|
@@ -33464,12 +33612,12 @@ function readsNothing(scope) {
|
|
|
33464
33612
|
return scope.kind === "none" || scope.kind === "key" && scope.apiKeyId === "";
|
|
33465
33613
|
}
|
|
33466
33614
|
// packages/control/src/quota/history.ts
|
|
33467
|
-
var
|
|
33615
|
+
var DAY_MS3 = 24 * 60 * 60 * 1000;
|
|
33468
33616
|
var HOUR_MS3 = 3600000;
|
|
33469
33617
|
async function retainedSpan(deps, input2, maxSpanMs) {
|
|
33470
33618
|
const now = deps.now();
|
|
33471
33619
|
const settings = await deps.store.config.getSettings();
|
|
33472
|
-
const retained = now - settings.logRetentionDays *
|
|
33620
|
+
const retained = now - settings.logRetentionDays * DAY_MS3;
|
|
33473
33621
|
const oldest = maxSpanMs === undefined ? retained : Math.max(retained, now - maxSpanMs);
|
|
33474
33622
|
return {
|
|
33475
33623
|
since: Math.max(optionalNumber(input2.since, oldest), oldest),
|
|
@@ -33912,7 +34060,11 @@ function createContext(parsed2, options = {}) {
|
|
|
33912
34060
|
const current = config2;
|
|
33913
34061
|
if (current === null)
|
|
33914
34062
|
throw new CliError(configError ?? "invalid configuration");
|
|
33915
|
-
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) => {
|
|
33916
34068
|
store = created;
|
|
33917
34069
|
return created;
|
|
33918
34070
|
});
|
|
@@ -34763,17 +34915,17 @@ var credentialsAddKey = {
|
|
|
34763
34915
|
};
|
|
34764
34916
|
var credentialsHealth = {
|
|
34765
34917
|
usage: "credentials health",
|
|
34766
|
-
summary: "Show breaker state
|
|
34918
|
+
summary: "Show breaker state per credential and model",
|
|
34767
34919
|
options: { all: { type: "boolean" } },
|
|
34768
34920
|
async run(args, { ctx, writer }) {
|
|
34769
34921
|
const store = await ctx.store();
|
|
34770
|
-
const [{ health: rows }, credentials] = await Promise.all([
|
|
34922
|
+
const [{ health: rows, lastUsed }, credentials] = await Promise.all([
|
|
34771
34923
|
credentialHealth({ store, now: ctx.now }),
|
|
34772
34924
|
listCredentials(store)
|
|
34773
34925
|
]);
|
|
34774
34926
|
const labels = new Map(credentials.map((c) => [c.id, c.label]));
|
|
34775
34927
|
const shown = boolFlag(args.values, "all") ? rows : rows.filter((r) => r.breakerState !== "closed" || r.consecutiveFailures > 0);
|
|
34776
|
-
emit(ctx, writer, { health: rows }, () => {
|
|
34928
|
+
emit(ctx, writer, { health: rows, lastUsed }, () => {
|
|
34777
34929
|
if (shown.length === 0)
|
|
34778
34930
|
return "every credential is healthy";
|
|
34779
34931
|
return table([
|
|
@@ -34781,15 +34933,13 @@ var credentialsHealth = {
|
|
|
34781
34933
|
{ header: "MODEL" },
|
|
34782
34934
|
{ header: "BREAKER" },
|
|
34783
34935
|
{ header: "FAILS", align: "right" },
|
|
34784
|
-
{ header: "TTFT", align: "right" },
|
|
34785
34936
|
{ header: "LAST USED" }
|
|
34786
34937
|
], shown.map((row) => [
|
|
34787
34938
|
labels.get(row.credentialId) ?? row.credentialId,
|
|
34788
34939
|
row.model,
|
|
34789
34940
|
state(ctx, row.breakerState === "closed", row.breakerState),
|
|
34790
34941
|
String(row.consecutiveFailures),
|
|
34791
|
-
|
|
34792
|
-
paint(ctx, "dim", formatTime(row.lastUsedAt))
|
|
34942
|
+
paint(ctx, "dim", formatTime(lastUsed[row.credentialId] ?? null))
|
|
34793
34943
|
]));
|
|
34794
34944
|
});
|
|
34795
34945
|
}
|
|
@@ -34830,7 +34980,8 @@ var dbMigrate = {
|
|
|
34830
34980
|
const source = await ctx.store();
|
|
34831
34981
|
const target = await createPostgresStore({
|
|
34832
34982
|
url: to,
|
|
34833
|
-
encryptionKey: await deriveKey(ctx.config().encryptionKey)
|
|
34983
|
+
encryptionKey: await deriveKey(ctx.config().encryptionKey),
|
|
34984
|
+
dayOffsetMinutes: ctx.config().dayOffsetMinutes
|
|
34834
34985
|
});
|
|
34835
34986
|
try {
|
|
34836
34987
|
const report = await copyStore(source, target);
|
|
@@ -35743,7 +35894,7 @@ function createServiceDeps(input2) {
|
|
|
35743
35894
|
}
|
|
35744
35895
|
|
|
35745
35896
|
// apps/cli/src/version.ts
|
|
35746
|
-
var VERSION = "0.
|
|
35897
|
+
var VERSION = "0.11.0";
|
|
35747
35898
|
|
|
35748
35899
|
// apps/cli/src/commands/service.ts
|
|
35749
35900
|
function gatewayArgv(root) {
|