omnigateway 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/bin/omni.js +101 -37
  2. package/gateway.js +212 -69
  3. package/package.json +1 -1
  4. package/public/assets/{Chip-D0YiqYkz.js → Chip-Ccqsb_Mq.js} +2 -2
  5. package/public/assets/{CopyValue-bzKz2tiJ.js → CopyValue-BhGx7Gy0.js} +5 -5
  6. package/public/assets/{Field-DdscQ9OO.js → Field-Devm4K46.js} +8 -8
  7. package/public/assets/Lamp-BXSUIAtG.js +25 -0
  8. package/public/assets/{Meter-Bm1oQE2U.js → Meter-Fn8X7t60.js} +2 -2
  9. package/public/assets/{Modal-Crr8uyWt.js → Modal-DOQGdhoM.js} +8 -8
  10. package/public/assets/{Rack-uDxrjvaz.js → Rack-DnbGnBvz.js} +18 -18
  11. package/public/assets/{Readout-CYg6UOop.js → Readout-BKpOZIGX.js} +5 -5
  12. package/public/assets/{States-DPTTYUHL.js → States-DBzsiHjQ.js} +10 -10
  13. package/public/assets/{Table-LkPaHBsj.js → Table-DPDJeeVc.js} +1 -1
  14. package/public/assets/{Toggle-D0e4C94X.js → Toggle-CP3RvAAB.js} +3 -3
  15. package/public/assets/_app-D83powx4.js +1 -0
  16. package/public/assets/_app.accounts-DR-2t9-7.js +51 -0
  17. package/public/assets/_app.index-DimCB0Bo.js +61 -0
  18. package/public/assets/{_app.keys-BNEOjtbu.js → _app.keys-B7kl3CSe.js} +8 -8
  19. package/public/assets/_app.logs-BjtdzcDY.js +18 -0
  20. package/public/assets/_app.models-BEECjcLM.js +144 -0
  21. package/public/assets/{_app.settings-C1gB4w7c.js → _app.settings-DtDIhAAr.js} +6 -6
  22. package/public/assets/{_app.usage-B0iHKzmI.js → _app.usage-BChnSBbS.js} +28 -28
  23. package/public/assets/index-C53PiRfb.js +170 -0
  24. package/public/assets/{login-D-FsWNX8.js → login-BuvJY1Qd.js} +8 -8
  25. package/public/assets/{queries-vY5lJqBe.js → queries-BMwc6EvM.js} +1 -1
  26. package/public/assets/{trash-2-Cep_aVLx.js → trash-2-Cq7ldH9D.js} +1 -1
  27. package/public/index.html +2 -2
  28. package/public/assets/Lamp-WOotyOTd.js +0 -15
  29. package/public/assets/_app-OSvB-Dv9.js +0 -1
  30. package/public/assets/_app.accounts-BOE49WyS.js +0 -51
  31. package/public/assets/_app.index-CTRlqxOq.js +0 -61
  32. package/public/assets/_app.logs-BxxqeEhM.js +0 -18
  33. package/public/assets/_app.models-BJi-RGUO.js +0 -144
  34. package/public/assets/index--dA3FFSN.js +0 -170
package/bin/omni.js CHANGED
@@ -17335,6 +17335,27 @@ ALTER TABLE credentials ADD COLUMN disabled_reason TEXT;
17335
17335
  ALTER TABLE credentials ADD COLUMN disabled_at INTEGER;
17336
17336
  `;
17337
17337
 
17338
+ // packages/store/src/sqlite/migrations/004_request_state.sql
17339
+ var _004_request_state_default = `-- Whether a request is still running.
17340
+ --
17341
+ -- A row used to be written once, after the response stream drained, so a long
17342
+ -- stream was invisible for its whole life and the console showed an idle
17343
+ -- gateway. A row is now written twice: \`pending\` when dispatch starts, \`done\`
17344
+ -- when it finishes.
17345
+ --
17346
+ -- Existing rows default to 'done'. Every row that predates this migration
17347
+ -- describes a request that has already ended, by definition.
17348
+ ALTER TABLE request_logs ADD COLUMN state TEXT NOT NULL DEFAULT 'done';
17349
+
17350
+ -- \`status\` and \`duration_ms\` stay NOT NULL: making them nullable means a table
17351
+ -- rebuild in SQLite, and a pending row's zeros are never read. Readers key off
17352
+ -- \`state\` alone.
17353
+ --
17354
+ -- Partial, because pending rows are a handful at any moment while the table
17355
+ -- holds a month of finished ones.
17356
+ CREATE INDEX idx_request_logs_pending ON request_logs(state) WHERE state = 'pending';
17357
+ `;
17358
+
17338
17359
  // packages/store/src/sqlite/rollup.ts
17339
17360
  function startOfLocalDay(at) {
17340
17361
  const day = new Date(at);
@@ -17441,7 +17462,8 @@ function backfillDaily(db) {
17441
17462
  var MIGRATIONS = [
17442
17463
  { id: 1, sql: _001_init_default },
17443
17464
  { id: 2, sql: _002_usage_daily_default, after: backfillDaily },
17444
- { id: 3, sql: _003_quota_snapshot_default }
17465
+ { id: 3, sql: _003_quota_snapshot_default },
17466
+ { id: 4, sql: _004_request_state_default }
17445
17467
  ];
17446
17468
  function openDb(path) {
17447
17469
  const db = new Database(path, { create: true });
@@ -17513,6 +17535,7 @@ function createKeyRepo(db) {
17513
17535
  // packages/store/src/sqlite/usage.ts
17514
17536
  var toLog = (r) => ({
17515
17537
  id: r.id,
17538
+ state: r.state === "pending" ? "pending" : "done",
17516
17539
  at: r.at,
17517
17540
  apiKeyId: r.api_key_id,
17518
17541
  requestedModel: r.requested_model,
@@ -17571,37 +17594,78 @@ function label(value) {
17571
17594
  const text = String(value);
17572
17595
  return text.length === 0 ? "unknown" : text;
17573
17596
  }
17597
+ var COLUMNS = `(id, state, at, api_key_id, requested_model, resolved_provider, resolved_model,
17598
+ credential_id, attempts, status, error_code, input_tokens, output_tokens,
17599
+ cache_read_tokens, cache_write_tokens, ttft_ms, duration_ms, cost_usd,
17600
+ degradations)`;
17601
+ var PLACEHOLDERS = "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
17602
+ function values(log, state) {
17603
+ return [
17604
+ log.id,
17605
+ state,
17606
+ log.at,
17607
+ log.apiKeyId,
17608
+ log.requestedModel,
17609
+ log.resolvedProvider,
17610
+ log.resolvedModel,
17611
+ log.credentialId,
17612
+ log.attempts,
17613
+ log.status,
17614
+ log.errorCode,
17615
+ log.inputTokens,
17616
+ log.outputTokens,
17617
+ log.cacheReadTokens,
17618
+ log.cacheWriteTokens,
17619
+ log.ttftMs,
17620
+ log.durationMs,
17621
+ log.costUsd,
17622
+ JSON.stringify(log.degradations)
17623
+ ];
17624
+ }
17625
+ var COMPLETE = `INSERT INTO request_logs ${COLUMNS} VALUES ${PLACEHOLDERS}
17626
+ ON CONFLICT(id) DO UPDATE SET
17627
+ state = 'done',
17628
+ requested_model = COALESCE(NULLIF(excluded.requested_model, ''), request_logs.requested_model),
17629
+ api_key_id = COALESCE(excluded.api_key_id, request_logs.api_key_id),
17630
+ resolved_provider = excluded.resolved_provider,
17631
+ resolved_model = excluded.resolved_model,
17632
+ credential_id = excluded.credential_id,
17633
+ attempts = excluded.attempts,
17634
+ status = excluded.status,
17635
+ error_code = excluded.error_code,
17636
+ input_tokens = excluded.input_tokens,
17637
+ output_tokens = excluded.output_tokens,
17638
+ cache_read_tokens = excluded.cache_read_tokens,
17639
+ cache_write_tokens = excluded.cache_write_tokens,
17640
+ ttft_ms = excluded.ttft_ms,
17641
+ duration_ms = excluded.duration_ms,
17642
+ cost_usd = excluded.cost_usd,
17643
+ degradations = excluded.degradations`;
17574
17644
  function createUsageRepo(db) {
17575
- const insert = db.transaction((log) => {
17576
- db.run(`INSERT INTO request_logs
17577
- (id, at, api_key_id, requested_model, resolved_provider, resolved_model, credential_id,
17578
- attempts, status, error_code, input_tokens, output_tokens, cache_read_tokens,
17579
- cache_write_tokens, ttft_ms, duration_ms, cost_usd, degradations)
17580
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, [
17581
- log.id,
17582
- log.at,
17583
- log.apiKeyId,
17584
- log.requestedModel,
17585
- log.resolvedProvider,
17586
- log.resolvedModel,
17587
- log.credentialId,
17588
- log.attempts,
17589
- log.status,
17590
- log.errorCode,
17591
- log.inputTokens,
17592
- log.outputTokens,
17593
- log.cacheReadTokens,
17594
- log.cacheWriteTokens,
17595
- log.ttftMs,
17596
- log.durationMs,
17597
- log.costUsd,
17598
- JSON.stringify(log.degradations)
17599
- ]);
17600
- rollupLog(db, log);
17645
+ const complete = db.transaction((log) => {
17646
+ db.run(COMPLETE, values(log, "done"));
17647
+ const stored = db.query("SELECT * FROM request_logs WHERE id = ?").get(log.id);
17648
+ if (stored !== null)
17649
+ rollupLog(db, toLog(stored));
17601
17650
  });
17602
17651
  return {
17652
+ async begin(log) {
17653
+ db.run(`INSERT INTO request_logs ${COLUMNS} VALUES ${PLACEHOLDERS}`, values(log, "pending"));
17654
+ },
17655
+ async route(id, target) {
17656
+ db.run(`UPDATE request_logs
17657
+ SET resolved_provider = ?, resolved_model = ?, credential_id = ?
17658
+ WHERE id = ? AND state = 'pending'`, [target.provider, target.model, target.credentialId, id]);
17659
+ },
17603
17660
  async append(log) {
17604
- insert(log);
17661
+ complete(log);
17662
+ },
17663
+ async sweepPending() {
17664
+ const stale = db.query("SELECT * FROM request_logs WHERE state = 'pending'").all().map(toLog);
17665
+ for (const log of stale) {
17666
+ complete({ ...log, status: 499, errorCode: "interrupted", durationMs: 0 });
17667
+ }
17668
+ return stale.length;
17605
17669
  },
17606
17670
  async recent(limit) {
17607
17671
  return db.query("SELECT * FROM request_logs ORDER BY at DESC LIMIT ?").all(limit).map(toLog);
@@ -17623,7 +17687,7 @@ function createUsageRepo(db) {
17623
17687
  COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,
17624
17688
  COALESCE(SUM(cost_usd), 0) AS cost_usd
17625
17689
  FROM ${daily ? "usage_daily" : "request_logs"}
17626
- WHERE ${timeColumn} >= ? AND ${timeColumn} <= ?
17690
+ WHERE ${daily ? "" : "state = 'done' AND "}${timeColumn} >= ? AND ${timeColumn} <= ?
17627
17691
  GROUP BY key${split === null ? "" : ", split"}
17628
17692
  ORDER BY requests DESC`).all(since, until);
17629
17693
  return rows.map((r) => {
@@ -18102,21 +18166,21 @@ function parse5(argv, options = {}) {
18102
18166
  throw new UsageError(error51 instanceof Error ? error51.message : "could not parse arguments");
18103
18167
  }
18104
18168
  }
18105
- function stringFlag(values, name) {
18106
- const value = values[name];
18169
+ function stringFlag(values2, name) {
18170
+ const value = values2[name];
18107
18171
  return typeof value === "string" ? value : undefined;
18108
18172
  }
18109
- function boolFlag(values, name) {
18110
- return values[name] === true;
18173
+ function boolFlag(values2, name) {
18174
+ return values2[name] === true;
18111
18175
  }
18112
- function listFlag(values, name) {
18113
- const value = values[name];
18176
+ function listFlag(values2, name) {
18177
+ const value = values2[name];
18114
18178
  if (Array.isArray(value))
18115
18179
  return value.filter((entry) => typeof entry === "string");
18116
18180
  return typeof value === "string" ? [value] : undefined;
18117
18181
  }
18118
- function numberFlag(values, name) {
18119
- const raw = stringFlag(values, name);
18182
+ function numberFlag(values2, name) {
18183
+ const raw = stringFlag(values2, name);
18120
18184
  if (raw === undefined)
18121
18185
  return;
18122
18186
  const value = Number(raw);
package/gateway.js CHANGED
@@ -22634,6 +22634,27 @@ ALTER TABLE credentials ADD COLUMN disabled_reason TEXT;
22634
22634
  ALTER TABLE credentials ADD COLUMN disabled_at INTEGER;
22635
22635
  `;
22636
22636
 
22637
+ // packages/store/src/sqlite/migrations/004_request_state.sql
22638
+ var _004_request_state_default = `-- Whether a request is still running.
22639
+ --
22640
+ -- A row used to be written once, after the response stream drained, so a long
22641
+ -- stream was invisible for its whole life and the console showed an idle
22642
+ -- gateway. A row is now written twice: \`pending\` when dispatch starts, \`done\`
22643
+ -- when it finishes.
22644
+ --
22645
+ -- Existing rows default to 'done'. Every row that predates this migration
22646
+ -- describes a request that has already ended, by definition.
22647
+ ALTER TABLE request_logs ADD COLUMN state TEXT NOT NULL DEFAULT 'done';
22648
+
22649
+ -- \`status\` and \`duration_ms\` stay NOT NULL: making them nullable means a table
22650
+ -- rebuild in SQLite, and a pending row's zeros are never read. Readers key off
22651
+ -- \`state\` alone.
22652
+ --
22653
+ -- Partial, because pending rows are a handful at any moment while the table
22654
+ -- holds a month of finished ones.
22655
+ CREATE INDEX idx_request_logs_pending ON request_logs(state) WHERE state = 'pending';
22656
+ `;
22657
+
22637
22658
  // packages/store/src/sqlite/rollup.ts
22638
22659
  function startOfLocalDay(at) {
22639
22660
  const day = new Date(at);
@@ -22740,7 +22761,8 @@ function backfillDaily(db) {
22740
22761
  var MIGRATIONS = [
22741
22762
  { id: 1, sql: _001_init_default },
22742
22763
  { id: 2, sql: _002_usage_daily_default, after: backfillDaily },
22743
- { id: 3, sql: _003_quota_snapshot_default }
22764
+ { id: 3, sql: _003_quota_snapshot_default },
22765
+ { id: 4, sql: _004_request_state_default }
22744
22766
  ];
22745
22767
  function openDb(path) {
22746
22768
  const db = new Database(path, { create: true });
@@ -22812,6 +22834,7 @@ function createKeyRepo(db) {
22812
22834
  // packages/store/src/sqlite/usage.ts
22813
22835
  var toLog = (r) => ({
22814
22836
  id: r.id,
22837
+ state: r.state === "pending" ? "pending" : "done",
22815
22838
  at: r.at,
22816
22839
  apiKeyId: r.api_key_id,
22817
22840
  requestedModel: r.requested_model,
@@ -22870,37 +22893,78 @@ function label(value) {
22870
22893
  const text = String(value);
22871
22894
  return text.length === 0 ? "unknown" : text;
22872
22895
  }
22896
+ var COLUMNS = `(id, state, at, api_key_id, requested_model, resolved_provider, resolved_model,
22897
+ credential_id, attempts, status, error_code, input_tokens, output_tokens,
22898
+ cache_read_tokens, cache_write_tokens, ttft_ms, duration_ms, cost_usd,
22899
+ degradations)`;
22900
+ var PLACEHOLDERS = "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
22901
+ function values(log, state) {
22902
+ return [
22903
+ log.id,
22904
+ state,
22905
+ log.at,
22906
+ log.apiKeyId,
22907
+ log.requestedModel,
22908
+ log.resolvedProvider,
22909
+ log.resolvedModel,
22910
+ log.credentialId,
22911
+ log.attempts,
22912
+ log.status,
22913
+ log.errorCode,
22914
+ log.inputTokens,
22915
+ log.outputTokens,
22916
+ log.cacheReadTokens,
22917
+ log.cacheWriteTokens,
22918
+ log.ttftMs,
22919
+ log.durationMs,
22920
+ log.costUsd,
22921
+ JSON.stringify(log.degradations)
22922
+ ];
22923
+ }
22924
+ var COMPLETE = `INSERT INTO request_logs ${COLUMNS} VALUES ${PLACEHOLDERS}
22925
+ ON CONFLICT(id) DO UPDATE SET
22926
+ state = 'done',
22927
+ requested_model = COALESCE(NULLIF(excluded.requested_model, ''), request_logs.requested_model),
22928
+ api_key_id = COALESCE(excluded.api_key_id, request_logs.api_key_id),
22929
+ resolved_provider = excluded.resolved_provider,
22930
+ resolved_model = excluded.resolved_model,
22931
+ credential_id = excluded.credential_id,
22932
+ attempts = excluded.attempts,
22933
+ status = excluded.status,
22934
+ error_code = excluded.error_code,
22935
+ input_tokens = excluded.input_tokens,
22936
+ output_tokens = excluded.output_tokens,
22937
+ cache_read_tokens = excluded.cache_read_tokens,
22938
+ cache_write_tokens = excluded.cache_write_tokens,
22939
+ ttft_ms = excluded.ttft_ms,
22940
+ duration_ms = excluded.duration_ms,
22941
+ cost_usd = excluded.cost_usd,
22942
+ degradations = excluded.degradations`;
22873
22943
  function createUsageRepo(db) {
22874
- const insert = db.transaction((log) => {
22875
- db.run(`INSERT INTO request_logs
22876
- (id, at, api_key_id, requested_model, resolved_provider, resolved_model, credential_id,
22877
- attempts, status, error_code, input_tokens, output_tokens, cache_read_tokens,
22878
- cache_write_tokens, ttft_ms, duration_ms, cost_usd, degradations)
22879
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, [
22880
- log.id,
22881
- log.at,
22882
- log.apiKeyId,
22883
- log.requestedModel,
22884
- log.resolvedProvider,
22885
- log.resolvedModel,
22886
- log.credentialId,
22887
- log.attempts,
22888
- log.status,
22889
- log.errorCode,
22890
- log.inputTokens,
22891
- log.outputTokens,
22892
- log.cacheReadTokens,
22893
- log.cacheWriteTokens,
22894
- log.ttftMs,
22895
- log.durationMs,
22896
- log.costUsd,
22897
- JSON.stringify(log.degradations)
22898
- ]);
22899
- rollupLog(db, log);
22944
+ const complete = db.transaction((log) => {
22945
+ db.run(COMPLETE, values(log, "done"));
22946
+ const stored = db.query("SELECT * FROM request_logs WHERE id = ?").get(log.id);
22947
+ if (stored !== null)
22948
+ rollupLog(db, toLog(stored));
22900
22949
  });
22901
22950
  return {
22951
+ async begin(log) {
22952
+ db.run(`INSERT INTO request_logs ${COLUMNS} VALUES ${PLACEHOLDERS}`, values(log, "pending"));
22953
+ },
22954
+ async route(id, target) {
22955
+ db.run(`UPDATE request_logs
22956
+ SET resolved_provider = ?, resolved_model = ?, credential_id = ?
22957
+ WHERE id = ? AND state = 'pending'`, [target.provider, target.model, target.credentialId, id]);
22958
+ },
22902
22959
  async append(log) {
22903
- insert(log);
22960
+ complete(log);
22961
+ },
22962
+ async sweepPending() {
22963
+ const stale = db.query("SELECT * FROM request_logs WHERE state = 'pending'").all().map(toLog);
22964
+ for (const log of stale) {
22965
+ complete({ ...log, status: 499, errorCode: "interrupted", durationMs: 0 });
22966
+ }
22967
+ return stale.length;
22904
22968
  },
22905
22969
  async recent(limit) {
22906
22970
  return db.query("SELECT * FROM request_logs ORDER BY at DESC LIMIT ?").all(limit).map(toLog);
@@ -22922,7 +22986,7 @@ function createUsageRepo(db) {
22922
22986
  COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,
22923
22987
  COALESCE(SUM(cost_usd), 0) AS cost_usd
22924
22988
  FROM ${daily ? "usage_daily" : "request_logs"}
22925
- WHERE ${timeColumn} >= ? AND ${timeColumn} <= ?
22989
+ WHERE ${daily ? "" : "state = 'done' AND "}${timeColumn} >= ? AND ${timeColumn} <= ?
22926
22990
  GROUP BY key${split === null ? "" : ", split"}
22927
22991
  ORDER BY requests DESC`).all(since, until);
22928
22992
  return rows.map((r) => {
@@ -31467,17 +31531,17 @@ var ElysiaType = {
31467
31531
  elysiaMeta: "ArrayString"
31468
31532
  })).Decode((value) => {
31469
31533
  if (Array.isArray(value)) {
31470
- let values = [];
31534
+ let values2 = [];
31471
31535
  for (let i = 0;i < value.length; i++) {
31472
31536
  const v = value[i];
31473
31537
  if (typeof v == "string") {
31474
31538
  const t2 = decode4(v, true);
31475
- Array.isArray(t2) ? values = values.concat(t2) : values.push(t2);
31539
+ Array.isArray(t2) ? values2 = values2.concat(t2) : values2.push(t2);
31476
31540
  continue;
31477
31541
  }
31478
- values.push(v);
31542
+ values2.push(v);
31479
31543
  }
31480
- return values;
31544
+ return values2;
31481
31545
  }
31482
31546
  return typeof value == "string" ? decode4(value) : value;
31483
31547
  }).Encode((value) => {
@@ -31498,17 +31562,17 @@ var ElysiaType = {
31498
31562
  elysiaMeta: "ArrayQuery"
31499
31563
  })).Decode((value) => {
31500
31564
  if (Array.isArray(value)) {
31501
- let values = [];
31565
+ let values2 = [];
31502
31566
  for (let i = 0;i < value.length; i++) {
31503
31567
  const v = value[i];
31504
31568
  if (typeof v == "string") {
31505
31569
  const t2 = decode4(v);
31506
- Array.isArray(t2) ? values = values.concat(t2) : values.push(t2);
31570
+ Array.isArray(t2) ? values2 = values2.concat(t2) : values2.push(t2);
31507
31571
  continue;
31508
31572
  }
31509
- values.push(v);
31573
+ values2.push(v);
31510
31574
  }
31511
- return values;
31575
+ return values2;
31512
31576
  }
31513
31577
  return typeof value == "string" ? decode4(value) : value;
31514
31578
  }).Encode((value) => {
@@ -31552,16 +31616,16 @@ var ElysiaType = {
31552
31616
  sign
31553
31617
  }, v;
31554
31618
  },
31555
- UnionEnum: (values, options = {}) => {
31556
- const type = values.every((value) => typeof value == "string") ? { type: "string" } : values.every((value) => typeof value == "number") ? { type: "number" } : values.every((value) => value === null) ? { type: "null" } : {};
31557
- if (values.some((x) => typeof x == "object" && x !== null))
31619
+ UnionEnum: (values2, options = {}) => {
31620
+ const type = values2.every((value) => typeof value == "string") ? { type: "string" } : values2.every((value) => typeof value == "number") ? { type: "number" } : values2.every((value) => value === null) ? { type: "null" } : {};
31621
+ if (values2.some((x) => typeof x == "object" && x !== null))
31558
31622
  throw new Error("This type does not support objects or arrays");
31559
31623
  return {
31560
- default: values[0],
31624
+ default: values2[0],
31561
31625
  ...options,
31562
31626
  [Kind]: "UnionEnum",
31563
31627
  ...type,
31564
- enum: values
31628
+ enum: values2
31565
31629
  };
31566
31630
  },
31567
31631
  NoValidate: (v, enabled = true) => (v.noValidate = enabled, v),
@@ -34007,33 +34071,33 @@ var getSchemaValidator = (s, {
34007
34071
  return v.then((v2) => Check22(v2, true));
34008
34072
  if (v.issues)
34009
34073
  return v;
34010
- const values = [];
34011
- return v && typeof v == "object" && values.push(v.value), runCheckers2(value, 0, values, v);
34012
- }, runCheckers2 = function(value, startIndex, values, lastV) {
34074
+ const values2 = [];
34075
+ return v && typeof v == "object" && values2.push(v.value), runCheckers2(value, 0, values2, v);
34076
+ }, runCheckers2 = function(value, startIndex, values2, lastV) {
34013
34077
  for (let i = startIndex;i < checkers.length; i++) {
34014
34078
  let v = checkers[i].validate(value);
34015
34079
  if (v instanceof Promise)
34016
34080
  return v.then((resolved) => {
34017
34081
  if (resolved.issues)
34018
34082
  return resolved;
34019
- const nextValues = [...values];
34083
+ const nextValues = [...values2];
34020
34084
  return resolved && typeof resolved == "object" && nextValues.push(resolved.value), runCheckers2(value, i + 1, nextValues, resolved);
34021
34085
  });
34022
34086
  if (v.issues)
34023
34087
  return v;
34024
- v && typeof v == "object" && values.push(v.value), lastV = v;
34088
+ v && typeof v == "object" && values2.push(v.value), lastV = v;
34025
34089
  }
34026
- return mergeValues22(values, lastV);
34027
- }, mergeValues22 = function(values, lastV) {
34028
- if (!values.length)
34090
+ return mergeValues22(values2, lastV);
34091
+ }, mergeValues22 = function(values2, lastV) {
34092
+ if (!values2.length)
34029
34093
  return { value: lastV };
34030
- if (values.length === 1)
34031
- return { value: values[0] };
34032
- if (values.length === 2)
34033
- return { value: mergeDeep(values[0], values[1]) };
34034
- let newValue = mergeDeep(values[0], values[1]);
34035
- for (let i = 2;i < values.length; i++)
34036
- newValue = mergeDeep(newValue, values[i]);
34094
+ if (values2.length === 1)
34095
+ return { value: values2[0] };
34096
+ if (values2.length === 2)
34097
+ return { value: mergeDeep(values2[0], values2[1]) };
34098
+ let newValue = mergeDeep(values2[0], values2[1]);
34099
+ for (let i = 2;i < values2.length; i++)
34100
+ newValue = mergeDeep(newValue, values2[i]);
34037
34101
  return { value: newValue };
34038
34102
  };
34039
34103
  var Check2 = Check22, runCheckers = runCheckers2, mergeValues2 = mergeValues22;
@@ -38228,6 +38292,7 @@ async function dispatch(request2, deps, signal) {
38228
38292
  const deadlineAt = startedAt + snapshot.settings.requestDeadlineMs;
38229
38293
  const log = {
38230
38294
  id: crypto.randomUUID(),
38295
+ state: "done",
38231
38296
  at: startedAt,
38232
38297
  apiKeyId: null,
38233
38298
  requestedModel: request2.model,
@@ -38319,6 +38384,11 @@ async function dispatch(request2, deps, signal) {
38319
38384
  log.credentialId = candidate.credential.id;
38320
38385
  log.resolvedProvider = candidate.target.provider;
38321
38386
  log.resolvedModel = candidate.target.model;
38387
+ await deps.onRoute?.({
38388
+ provider: candidate.target.provider,
38389
+ model: candidate.target.model,
38390
+ credentialId: candidate.credential.id
38391
+ });
38322
38392
  log.inputTokens = 0;
38323
38393
  log.outputTokens = 0;
38324
38394
  log.cacheReadTokens = 0;
@@ -39116,18 +39186,44 @@ function parseOpenAIRequest(body2) {
39116
39186
  }
39117
39187
 
39118
39188
  // apps/gateway/src/logging.ts
39189
+ function report(what, requestId, error51) {
39190
+ console.error(what, {
39191
+ requestId,
39192
+ reason: error51 instanceof Error ? error51.message : "unknown"
39193
+ });
39194
+ }
39195
+ async function beginLog(store, log, keyId) {
39196
+ try {
39197
+ await store.usage.begin({ ...log, state: "pending", apiKeyId: keyId });
39198
+ } catch (error51) {
39199
+ report("failed to record request start", log.id, error51);
39200
+ }
39201
+ }
39202
+ async function routeLog(store, requestId, target) {
39203
+ try {
39204
+ await store.usage.route(requestId, target);
39205
+ } catch (error51) {
39206
+ report("failed to record request route", requestId, error51);
39207
+ }
39208
+ }
39119
39209
  async function finishLog(store, log, keyId) {
39120
39210
  try {
39121
39211
  await store.usage.append({ ...log, apiKeyId: keyId });
39122
39212
  } catch (error51) {
39123
- console.error("failed to persist request log", {
39124
- requestId: log.id,
39125
- reason: error51 instanceof Error ? error51.message : "unknown"
39126
- });
39213
+ report("failed to persist request log", log.id, error51);
39127
39214
  }
39128
39215
  }
39129
39216
 
39130
39217
  // apps/gateway/src/routes/proxy.ts
39218
+ var KEEPALIVE_MS = 1e4;
39219
+ var KEEPALIVE = Symbol("keepalive");
39220
+ function withKeepalive(pending, ms) {
39221
+ let timer;
39222
+ const tick = new Promise((resolve) => {
39223
+ timer = setTimeout(() => resolve(KEEPALIVE), ms);
39224
+ });
39225
+ return Promise.race([pending, tick]).finally(() => clearTimeout(timer));
39226
+ }
39131
39227
  var SSE_HEADERS = {
39132
39228
  "content-type": "text/event-stream; charset=utf-8",
39133
39229
  "cache-control": "no-cache, no-transform",
@@ -39146,7 +39242,7 @@ function asGatewayError2(error51) {
39146
39242
  return error51;
39147
39243
  return new GatewayError("INTERNAL", error51 instanceof Error ? error51.message : "internal error");
39148
39244
  }
39149
- function sseResponse(frames, onDone) {
39245
+ function sseResponse(frames, onDone, keepaliveMs) {
39150
39246
  const encoder2 = new TextEncoder;
39151
39247
  let done = null;
39152
39248
  const runOnce = () => {
@@ -39154,10 +39250,19 @@ function sseResponse(frames, onDone) {
39154
39250
  done = onDone();
39155
39251
  return done;
39156
39252
  };
39253
+ let inflight = null;
39157
39254
  const stream2 = new ReadableStream({
39158
39255
  async pull(controller) {
39159
39256
  try {
39160
- const next = await frames.next();
39257
+ inflight ??= frames.next();
39258
+ const next = await withKeepalive(inflight, keepaliveMs);
39259
+ if (next === KEEPALIVE) {
39260
+ controller.enqueue(encoder2.encode(`: keepalive
39261
+
39262
+ `));
39263
+ return;
39264
+ }
39265
+ inflight = null;
39161
39266
  if (next.done === true) {
39162
39267
  controller.close();
39163
39268
  await runOnce();
@@ -39182,7 +39287,9 @@ data: ${data}
39182
39287
  }
39183
39288
  async function handle(deps, rateLimiter, surface, request2) {
39184
39289
  const requestId = deps.requestId();
39290
+ const startedAt = deps.now();
39185
39291
  let keyId = null;
39292
+ let requestedModel = "";
39186
39293
  try {
39187
39294
  const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
39188
39295
  keyId = key.id;
@@ -39192,11 +39299,42 @@ async function handle(deps, rateLimiter, surface, request2) {
39192
39299
  if (key.modelAllowlist !== null && !key.modelAllowlist.includes(chatRequest.model)) {
39193
39300
  throw new GatewayError("AUTH", `model "${chatRequest.model}" is not allowed for this API key`);
39194
39301
  }
39195
- const outcome = await dispatch(chatRequest, deps, request2.signal);
39302
+ requestedModel = chatRequest.model;
39303
+ let began = false;
39304
+ const outcome = await dispatch(chatRequest, {
39305
+ ...deps,
39306
+ async onRoute(target) {
39307
+ if (began) {
39308
+ await routeLog(deps.store, requestId, target);
39309
+ return;
39310
+ }
39311
+ began = true;
39312
+ await beginLog(deps.store, {
39313
+ id: requestId,
39314
+ at: startedAt,
39315
+ apiKeyId: keyId,
39316
+ requestedModel,
39317
+ resolvedProvider: target.provider,
39318
+ resolvedModel: target.model,
39319
+ credentialId: target.credentialId,
39320
+ attempts: 0,
39321
+ status: 0,
39322
+ errorCode: null,
39323
+ inputTokens: 0,
39324
+ outputTokens: 0,
39325
+ cacheReadTokens: 0,
39326
+ cacheWriteTokens: 0,
39327
+ ttftMs: null,
39328
+ durationMs: 0,
39329
+ costUsd: 0,
39330
+ degradations: []
39331
+ }, keyId);
39332
+ }
39333
+ }, request2.signal);
39196
39334
  const log = () => finishLog(deps.store, { ...outcome.log(), id: requestId }, keyId);
39197
39335
  if (chatRequest.stream) {
39198
39336
  const frames = surface === "anthropic" ? anthropicStream(outcome.events, requestId) : openaiStream(outcome.events, requestId, Math.floor(deps.now() / 1000));
39199
- return sseResponse(frames, log);
39337
+ return sseResponse(frames, log, deps.keepaliveMs);
39200
39338
  }
39201
39339
  const events = [];
39202
39340
  for await (const event of outcome.events)
@@ -39215,9 +39353,10 @@ async function handle(deps, rateLimiter, surface, request2) {
39215
39353
  const gatewayError = asGatewayError2(error51);
39216
39354
  await finishLog(deps.store, {
39217
39355
  id: requestId,
39218
- at: deps.now(),
39356
+ state: "done",
39357
+ at: startedAt,
39219
39358
  apiKeyId: keyId,
39220
- requestedModel: "",
39359
+ requestedModel,
39221
39360
  resolvedProvider: null,
39222
39361
  resolvedModel: null,
39223
39362
  credentialId: null,
@@ -39240,7 +39379,8 @@ function proxyRoutes(deps) {
39240
39379
  const rateLimiter = deps.rateLimiter ?? new ApiKeyRateLimiter(deps.now);
39241
39380
  const dispatchDeps = {
39242
39381
  ...deps,
39243
- snapshots: deps.snapshots ?? createRoutingSnapshotCache(deps.store)
39382
+ snapshots: deps.snapshots ?? createRoutingSnapshotCache(deps.store),
39383
+ keepaliveMs: deps.keepaliveMs ?? KEEPALIVE_MS
39244
39384
  };
39245
39385
  return new Elysia().post("/v1/messages", ({ request: request2 }) => handle(dispatchDeps, rateLimiter, "anthropic", request2)).post("/v1/chat/completions", ({ request: request2 }) => handle(dispatchDeps, rateLimiter, "openai", request2)).get("/v1/models", async ({ request: request2 }) => {
39246
39386
  try {
@@ -39469,6 +39609,9 @@ var store = await createStore({
39469
39609
  path: config2.databasePath,
39470
39610
  encryptionKey
39471
39611
  });
39612
+ var swept = await store.usage.sweepPending();
39613
+ if (swept > 0)
39614
+ console.log(`retired ${swept} request(s) interrupted by the last shutdown`);
39472
39615
  var now = () => Date.now();
39473
39616
  var http = nodeHttpClient();
39474
39617
  var refresh = createRefresher({ store, providers: OAUTH_PROVIDERS, http, now });
@@ -39489,7 +39632,7 @@ var stopQuotaPoller = await startQuotaPoller({
39489
39632
  refresh,
39490
39633
  now
39491
39634
  });
39492
- app.listen({ port: config2.port, hostname: config2.host });
39635
+ app.listen({ port: config2.port, hostname: config2.host, idleTimeout: 255 });
39493
39636
  console.log(`omnigateway listening on http://${config2.host}:${config2.port}`);
39494
39637
  var shuttingDown = false;
39495
39638
  function exitAfterClosingStore(code) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnigateway",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Self-hosted AI gateway with Anthropic- and OpenAI-compatible APIs, an admin console, and a CLI",
5
5
  "license": "MIT",
6
6
  "author": "Harismawan <mail@harismawan.com>",
@@ -1,4 +1,4 @@
1
- import{B as e,Dt as t}from"./queries-vY5lJqBe.js";var n=t(),r={neutral:{fg:`var(--ink-dim)`,bg:`transparent`},ok:{fg:`var(--ok)`,bg:`var(--ok-wash)`},warn:{fg:`var(--warn)`,bg:`var(--warn-wash)`},down:{fg:`var(--down)`,bg:`var(--down-wash)`},accent:{fg:`var(--accent)`,bg:`var(--accent-wash)`}},i=e.span`
1
+ import{Ot as e,V as t}from"./queries-BMwc6EvM.js";var n=e(),r={neutral:{fg:`var(--ink-dim)`,bg:`transparent`},ok:{fg:`var(--ok)`,bg:`var(--ok-wash)`},warn:{fg:`var(--warn)`,bg:`var(--warn-wash)`},down:{fg:`var(--down)`,bg:`var(--down-wash)`},accent:{fg:`var(--accent)`,bg:`var(--accent-wash)`}},i=t.span`
2
2
  display: inline-flex;
3
3
  align-items: center;
4
4
  gap: 4px;
@@ -12,7 +12,7 @@ import{B as e,Dt as t}from"./queries-vY5lJqBe.js";var n=t(),r={neutral:{fg:`var(
12
12
  font-size: 10.5px;
13
13
  letter-spacing: 0.02em;
14
14
  white-space: nowrap;
15
- `,a=e.span`
15
+ `,a=t.span`
16
16
  display: inline-flex;
17
17
  align-items: center;
18
18
  gap: 5px;