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.
Files changed (34) hide show
  1. package/README.md +20 -2
  2. package/bin/omni.js +454 -303
  3. package/gateway.js +593 -360
  4. package/package.json +1 -1
  5. package/public/assets/{Chip-BeKVtewW.js → Chip-CmL-9rw7.js} +1 -1
  6. package/public/assets/{CopyValue-NTHhiCuT.js → CopyValue-CTVbU0O_.js} +1 -1
  7. package/public/assets/{Lamp-C2Q3t5Mn.js → Lamp-DtHKJH6P.js} +1 -1
  8. package/public/assets/{Rack-sUGRoZUx.js → Rack-_A_3zqxC.js} +1 -1
  9. package/public/assets/{RequestTable-C2RLARWy.js → RequestTable-Cokk7wKP.js} +1 -1
  10. package/public/assets/{SummaryDeck-BHXcb_Ji.js → SummaryDeck-C0IbqnLz.js} +1 -1
  11. package/public/assets/{Toggle-DwKz1aVq.js → Toggle-ChjN-tw1.js} +1 -1
  12. package/public/assets/{TokenBreakdown-DM-1HvOr.js → TokenBreakdown-BPoXM3JM.js} +1 -1
  13. package/public/assets/{WindowChart-BsonO5Py.js → WindowChart-BPEsEGFL.js} +1 -1
  14. package/public/assets/_app.accounts-DKmxz7Tl.js +59 -0
  15. package/public/assets/{_app.console-BQJMLRv4.js → _app.console-Bkdallia.js} +1 -1
  16. package/public/assets/{_app.database-BIiajZm6.js → _app.database-DxqN1395.js} +1 -1
  17. package/public/assets/{_app.index-DagK0Pqp.js → _app.index-BUqor-qH.js} +3 -3
  18. package/public/assets/{_app.keys-DLAG_bmt.js → _app.keys-DMCPEj1g.js} +1 -1
  19. package/public/assets/{_app.logs-AyVq3lLb.js → _app.logs-DGQD8ZAq.js} +1 -1
  20. package/public/assets/{_app.models-CwlkGPrx.js → _app.models-D6SV_GVk.js} +1 -1
  21. package/public/assets/{_app.plugins._pluginId-BhvEnawY.js → _app.plugins._pluginId-CJ7ObXXe.js} +1 -1
  22. package/public/assets/{_app.settings-BKA-1YLs.js → _app.settings-BVimpngR.js} +1 -1
  23. package/public/assets/{_app.usage-C7JEWKZM.js → _app.usage-Y2Nkaw0t.js} +1 -1
  24. package/public/assets/{chevron-right-S84SGCvk.js → chevron-right-BfnK63GB.js} +1 -1
  25. package/public/assets/{client-DCvpw0LS.js → client-WgQ5A2PD.js} +1 -1
  26. package/public/assets/{index-pZbadEun.js → index-BTE8vyBz.js} +3 -3
  27. package/public/assets/{login-CitmheGz.js → login-CrJK9wRA.js} +1 -1
  28. package/public/assets/plus-Cv51cK5k.js +1 -0
  29. package/public/assets/{preload-helper-BC9bKUg1.js → preload-helper-BknjH67x.js} +1 -1
  30. package/public/assets/{shared-C47BfV_u.js → shared-oTMOLWMT.js} +1 -1
  31. package/public/assets/{trash-2-DmtElUHr.js → trash-2-CSkf6ME_.js} +1 -1
  32. package/public/index.html +4 -4
  33. package/public/assets/_app.accounts-DUYxItf0.js +0 -59
  34. 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 = encoder2.encode(json2).length;
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 (encoder2.encode(omittedJson).length <= MAX_ARTIFACT_BYTES) {
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 = [{ id: 1, sql: _001_init_default }];
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, ewma_ttft_ms, last_used_at)
20148
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8)
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
- startOfLocalDay(log.at),
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 ? startOfLocalDay(q.since) : q.since;
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, ewma_ttft_ms, last_used_at)
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, next.ewmaTtftMs, next.lastUsedAt);
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 openDb(path) {
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
- db.run(m.sql);
22600
- m.after?.(db);
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 ? startOfLocalDay(q.since) : q.since;
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 system = systemText(req.system, "antigravity", note2);
26801
- if (system !== undefined && system.length > 0) {
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 h = snapshot.health.get(healthKey(p.credential.id, p.target.model));
31282
- return h?.ewmaTtftMs != null ? [h.ewmaTtftMs] : [];
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 latency = h?.ewmaTtftMs == null || h.ewmaTtftMs <= 0 || bestLatency === null ? UNKNOWN : ratio(h.ewmaTtftMs, bestLatency);
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 h = input2.snapshot.health.get(healthKey(c.credential.id, c.target.model));
31340
- return h?.lastUsedAt == null ? Number.POSITIVE_INFINITY : input2.now - h.lastUsedAt;
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 DAY_MS = 86400000;
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 * DAY_MS;
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 DAY_MS2 = 24 * 60 * 60 * 1000;
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 * DAY_MS2;
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({ path: databasePath, encryptionKey: key })).then((created) => {
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 and latency per credential and model",
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
- row.ewmaTtftMs === null ? "\u2014" : `${Math.round(row.ewmaTtftMs)}ms`,
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.10.2";
35897
+ var VERSION = "0.11.0";
35747
35898
 
35748
35899
  // apps/cli/src/commands/service.ts
35749
35900
  function gatewayArgv(root) {