omnigateway 0.8.0 → 0.9.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 (48) hide show
  1. package/README.md +25 -4
  2. package/bin/omni.js +112 -22
  3. package/gateway.js +103 -21
  4. package/package.json +1 -1
  5. package/public/assets/{Chip-DG-mqRx0.js → Chip-shturdCv.js} +1 -1
  6. package/public/assets/{CopyValue-CItB094V.js → CopyValue-BGt9RRTL.js} +1 -1
  7. package/public/assets/{Field-DK5zaBVA.js → Field-DcNu4yw-.js} +1 -1
  8. package/public/assets/Lamp-CVGUT2YK.js +25 -0
  9. package/public/assets/{Panel-CYQvfI3L.js → Panel-BlNr9j6-.js} +3 -1
  10. package/public/assets/{Rack-xBT_6EtM.js → Rack-BbZyyu2u.js} +5 -5
  11. package/public/assets/{Readout-D6-aurU8.js → Readout-D8Ofl3Gz.js} +1 -1
  12. package/public/assets/{RequestTable-CM9mPiMF.js → RequestTable-huSAoNZg.js} +1 -1
  13. package/public/assets/{SummaryDeck-C3ZwZ3ng.js → SummaryDeck-BPo2bMSC.js} +1 -1
  14. package/public/assets/{Toggle-CmXXn6F3.js → Toggle-C84xs_YB.js} +1 -1
  15. package/public/assets/{TokenBreakdown-COJ643vg.js → TokenBreakdown-CmzXuENj.js} +1 -1
  16. package/public/assets/{WindowChart-BrLf4KCu.js → WindowChart-BZzVrxYk.js} +1 -1
  17. package/public/assets/{_app-BWNEo1e-.js → _app-CWXzHdhc.js} +1 -1
  18. package/public/assets/_app.accounts-DMmtkwnl.js +59 -0
  19. package/public/assets/_app.console-BBz3kCPV.js +44 -0
  20. package/public/assets/_app.database-ZvtMVgsh.js +24 -0
  21. package/public/assets/{_app.index-CB3UFRwF.js → _app.index-DKouD-rz.js} +5 -5
  22. package/public/assets/{_app.keys-B89CMo3x.js → _app.keys-BG9-zfh6.js} +5 -5
  23. package/public/assets/{_app.logs-CNAnRW07.js → _app.logs-DjinYGYS.js} +2 -2
  24. package/public/assets/{_app.models-DpRcGhag.js → _app.models-BCfQVuoT.js} +5 -5
  25. package/public/assets/_app.plugins._pluginId-B9k5eqAt.js +2 -0
  26. package/public/assets/{_app.plugins._pluginId-sYDTZ9Px.js → _app.plugins._pluginId-Dm48f3wz.js} +1 -1
  27. package/public/assets/{_app.settings-BV085FpM.js → _app.settings-C42ylcNP.js} +4 -4
  28. package/public/assets/{_app.usage-CGuOdYun.js → _app.usage-DfPM6eQ-.js} +3 -3
  29. package/public/assets/{chevron-right-BeVtzzum.js → chevron-right-HI_fK6Gw.js} +1 -1
  30. package/public/assets/client-TUf0nuim.js +39 -0
  31. package/public/assets/index-C66Zn0tX.js +13 -0
  32. package/public/assets/login-DWgivEPK.js +2 -0
  33. package/public/assets/{login-BpvnkW6i.js → login-w-hVstKf.js} +2 -2
  34. package/public/assets/plus-BCf2TPqz.js +1 -0
  35. package/public/assets/{preload-helper-8jyVfd2a.js → preload-helper-Unv7FI91.js} +1 -1
  36. package/public/assets/{shared-B2Lmnazz.js → shared-XRnr7At9.js} +1 -1
  37. package/public/assets/{stream-VJMOV03S.js → stream-3PafhwhZ.js} +1 -1
  38. package/public/assets/{trash-2-CbjLfrko.js → trash-2-Bq4GaXzx.js} +1 -1
  39. package/public/index.html +8 -8
  40. package/public/assets/Lamp-Cxp-OxUq.js +0 -25
  41. package/public/assets/_app.accounts-B-OYO2Yi.js +0 -59
  42. package/public/assets/_app.console-BOZ1wncA.js +0 -44
  43. package/public/assets/_app.database-B5oHZPbu.js +0 -24
  44. package/public/assets/_app.plugins._pluginId-LzBq-Zjj.js +0 -2
  45. package/public/assets/client-Mf5f5R9Z.js +0 -39
  46. package/public/assets/index-3lpol4cr.js +0 -13
  47. package/public/assets/login-CwYbEKWH.js +0 -2
  48. package/public/assets/plus-nHMFjQ2I.js +0 -1
package/README.md CHANGED
@@ -388,6 +388,7 @@ Use `--db <path>` to point one command somewhere else.
388
388
  | `omni db backup` / `snapshots` | take a snapshot, and list the ones retention has kept |
389
389
  | `omni db restore <id>` | put a snapshot back; shows a live-vs-snapshot count table, then asks. `--dry-run` shows the table and stops |
390
390
  | `omni db vacuum` | rewrite the database, reclaiming the pages deletion left free |
391
+ | `omni db clear-bodies` | delete every captured request and response body; the requests stay logged |
391
392
 
392
393
  Two worth knowing:
393
394
 
@@ -516,6 +517,15 @@ bun apps/gateway/src/index.ts >> /var/log/omni.log 2>&1
516
517
  `omni start` does both for the gateway it supervises, and under systemd the journal needs no
517
518
  setup.
518
519
 
520
+ In a fleet, capture is per process. Each replica can capture its own — tee its stdout to a file
521
+ inside the container and point `OMNI_LOG_FILE` at the same path — and the Console screen then
522
+ merges every process's tail and lets you pick one. That is worth having for an incident on a
523
+ running pod, and it is not a log stack: the file dies with the container, nothing rotates it, and
524
+ the screen reads one process at a time. Ship stdout to a collector for anything beyond that —
525
+ Elasticsearch and Kibana, Loki and Grafana, or whatever already reads your containers — where the
526
+ lines outlive the process that wrote them and can be searched across all of them at once. The
527
+ Console screen says so itself when it finds a fleet capturing nothing.
528
+
519
529
  Everything else lives in the database rather than the environment, so it can be
520
530
  changed without a restart: the six routing weights, `maxAttempts`,
521
531
  `requestDeadlineMs`, the circuit breaker's `breakerThreshold` and
@@ -641,9 +651,12 @@ format, structural bounds, and masking rules.
641
651
  ## Snapshots and restore
642
652
 
643
653
  The console's Database screen reports what this installation occupies — the
644
- database file, its write-ahead log, the captured-body tree, and the free pages a
645
- compaction would give back and takes snapshots. `omni db stats` prints the same
646
- figures.
654
+ database file, its write-ahead log, the captured-body tree, the free pages a
655
+ compaction would give back, and every table by size and takes snapshots.
656
+ `omni db stats` prints the same figures. On Postgres both show the server's own
657
+ size, the `request_bodies` table and the per-table listing instead; there is no
658
+ file, so nothing here compacts, snapshots or restores it — `pg_dump` is the
659
+ backup.
647
660
 
648
661
  **What a snapshot is.** One self-contained SQLite file, written into a
649
662
  `snapshots/` directory beside the database. The write-ahead log is folded in, so
@@ -704,6 +717,12 @@ for the rewrite, so a busy gateway stalls on its writes until it finishes — bu
704
717
  nothing is lost by running it live, and it reports what it actually gave back to
705
718
  the filesystem.
706
719
 
720
+ **Clearing bodies.** `omni db clear-bodies`, or the Database screen's *Clear
721
+ bodies*, deletes every captured prompt and completion on either engine — the
722
+ files beside a SQLite database, the `request_bodies` rows on Postgres. The
723
+ requests stay in the log with their bodies marked pruned. There is no undo:
724
+ bodies are never in a snapshot.
725
+
707
726
  ## Docker
708
727
 
709
728
  ```bash
@@ -737,7 +756,9 @@ and the workflow commits that version into `k8s/kustomization.yaml` on `main`, w
737
756
  CD syncs. Rolling back is editing `newTag` by hand.
738
757
 
739
758
  Plugins in a fleet are baked into the image so every replica holds the same
740
- set: `COPY plugins/ /data/plugins/` in a derived Dockerfile.
759
+ set: `COPY plugins/ /data/plugins/` in a derived Dockerfile. The image is
760
+ public, so no pull secret is configured; a private fork adds
761
+ `imagePullSecrets` to the Deployment.
741
762
 
742
763
  Give the container a restart policy — `--restart unless-stopped` — if you want a
743
764
  restart request to bring it back. A container cannot read its own policy, so
package/bin/omni.js CHANGED
@@ -16281,6 +16281,23 @@ function createMaintenanceRepo(sql, nodeId) {
16281
16281
  schemaVersion: row?.version ?? 0
16282
16282
  };
16283
16283
  },
16284
+ async tables() {
16285
+ const rows = await sql.unsafe(`SELECT c.relname AS name,
16286
+ pg_total_relation_size(c.oid) AS bytes,
16287
+ GREATEST(c.reltuples, 0)::bigint AS rows,
16288
+ COALESCE(s.n_dead_tup, 0) AS dead_rows
16289
+ FROM pg_class c
16290
+ JOIN pg_namespace n ON n.oid = c.relnamespace
16291
+ LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid
16292
+ WHERE c.relkind = 'r' AND n.nspname = current_schema()
16293
+ ORDER BY bytes DESC, name`);
16294
+ return rows.map((row) => ({
16295
+ name: row.name,
16296
+ bytes: num(row.bytes),
16297
+ rows: num(row.rows),
16298
+ deadRows: num(row.dead_rows)
16299
+ }));
16300
+ },
16284
16301
  vacuum: async () => unsupported(),
16285
16302
  snapshotTo: async () => unsupported(),
16286
16303
  inspect: async () => unsupported()
@@ -17219,6 +17236,7 @@ async function createPostgresStore(opts) {
17219
17236
  });
17220
17237
  };
17221
17238
  return {
17239
+ engine: "postgres",
17222
17240
  databasePath: redacted(opts.url),
17223
17241
  credentials: createCredentialRepo(sql, opts.encryptionKey, emit2),
17224
17242
  config: createConfigRepo(sql, emit2),
@@ -18401,6 +18419,15 @@ function createMaintenanceRepo2(db, nodeId) {
18401
18419
  schemaVersion: version2?.version ?? 0
18402
18420
  };
18403
18421
  },
18422
+ async tables() {
18423
+ return db.query(`SELECT m.tbl_name AS name,
18424
+ SUM(s.pgsize) AS bytes,
18425
+ SUM(CASE WHEN s.pagetype = 'leaf' AND s.name = m.tbl_name THEN s.ncell ELSE 0 END) AS rows
18426
+ FROM dbstat s JOIN sqlite_master m ON m.name = s.name
18427
+ WHERE m.tbl_name NOT LIKE 'sqlite_%'
18428
+ GROUP BY m.tbl_name
18429
+ ORDER BY bytes DESC, name`).all().map((row) => ({ ...row, deadRows: null }));
18430
+ },
18404
18431
  async vacuum() {
18405
18432
  db.run("VACUUM");
18406
18433
  db.run("PRAGMA wal_checkpoint(TRUNCATE)");
@@ -18872,6 +18899,7 @@ async function createStore(opts) {
18872
18899
  live = false;
18873
18900
  };
18874
18901
  return {
18902
+ engine: "sqlite",
18875
18903
  databasePath: opts.path,
18876
18904
  credentials: {
18877
18905
  list: () => handle.credentials.list(),
@@ -18936,6 +18964,7 @@ async function createStore(opts) {
18936
18964
  },
18937
18965
  maintenance: {
18938
18966
  stats: () => handle.maintenance.stats(),
18967
+ tables: () => handle.maintenance.tables(),
18939
18968
  heartbeat: (now) => handle.maintenance.heartbeat(now),
18940
18969
  nodes: (now) => handle.maintenance.nodes(now),
18941
18970
  vacuum: () => handle.maintenance.vacuum(),
@@ -20975,13 +21004,13 @@ function encodeSystemTurn(content, cloak) {
20975
21004
  return content.map((block) => encodeBlock(block, cloak));
20976
21005
  }
20977
21006
  function systemCacheControl(req) {
20978
- const cacheable = req.messages.flatMap((message) => message.content.flatMap((block) => block.type === "thinking" ? [] : [{ role: message.role, block }]));
20979
- const markedSystemBlocks = cacheable.filter(({ role, block }) => role === "system" && cacheControlOf(block) !== undefined);
20980
- const final = markedSystemBlocks.at(-1);
20981
- const promoted = final !== undefined && final.block.type === "text" && cacheable.at(-1) === final ? cacheControlOf(final.block) : undefined;
21007
+ const markedSystemBlocks = req.messages.flatMap((message) => message.role === "system" ? message.content.filter((block) => cacheControlOf(block) !== undefined) : []);
21008
+ const trailing = req.messages.at(-1);
21009
+ const marked = trailing?.role === "system" ? trailing.content.findLast((block) => cacheControlOf(block) !== undefined) : undefined;
21010
+ const retarget = marked === undefined ? undefined : cacheControlOf(marked);
20982
21011
  return {
20983
- ...promoted === undefined ? {} : { promoted },
20984
- lost: markedSystemBlocks.length > (promoted === undefined ? 0 : 1)
21012
+ ...retarget === undefined ? {} : { retarget },
21013
+ lost: markedSystemBlocks.length > (retarget === undefined ? 0 : 1)
20985
21014
  };
20986
21015
  }
20987
21016
  function encodeTool(t, cloak) {
@@ -21143,14 +21172,26 @@ function toWire(req, model, opts) {
21143
21172
  return [{ role: m.role, content: encodeSystemTurn(m.content, cloak) }];
21144
21173
  }),
21145
21174
  max_tokens: req.maxTokens ?? 4096,
21146
- stream: req.stream,
21147
- ...systemCache.promoted === undefined ? {} : {
21148
- cache_control: {
21149
- type: systemCache.promoted.type,
21150
- ...systemCache.promoted.ttl === undefined ? {} : { ttl: systemCache.promoted.ttl }
21175
+ stream: req.stream
21176
+ };
21177
+ if (systemCache.retarget !== undefined) {
21178
+ const trailing = body.messages.at(-1);
21179
+ if (isRecord(trailing) && Array.isArray(trailing.content)) {
21180
+ for (const block of trailing.content) {
21181
+ if (isRecord(block))
21182
+ delete block.cache_control;
21151
21183
  }
21152
21184
  }
21153
- };
21185
+ if (!hasVendorCacheControl(req)) {
21186
+ const target = lastCacheableHistoryBlock(body.messages.slice(0, -1));
21187
+ if (target === undefined) {
21188
+ note2("anthropic:system-turn-cache-control-dropped");
21189
+ } else if (target.cache_control === undefined) {
21190
+ Object.assign(target, wireCacheControl(systemCache.retarget));
21191
+ note2("anthropic:system-turn-cache-control-retargeted");
21192
+ }
21193
+ }
21194
+ }
21154
21195
  if (system !== undefined && system.length > 0)
21155
21196
  body.system = system;
21156
21197
  if (req.temperature !== undefined)
@@ -25398,6 +25439,11 @@ async function restoreSnapshot(deps, id) {
25398
25439
  return withExclusive("a restore", () => swapIn(deps, { path, consume: false }));
25399
25440
  }
25400
25441
  var MAX_IMPORT_BYTES = 2 * 1024 * 1024 * 1024;
25442
+ async function clearBodies(deps) {
25443
+ const removed = await deps.store.bodies.prune(deps.now());
25444
+ const orphans = await deps.store.bodies.sweepOrphans();
25445
+ return { removed, orphans };
25446
+ }
25401
25447
  async function vacuum(deps) {
25402
25448
  return withExclusive("a vacuum", async () => {
25403
25449
  const before = deps.fs.stat(deps.store.databasePath)?.size ?? 0;
@@ -25412,18 +25458,37 @@ async function vacuum(deps) {
25412
25458
  }
25413
25459
  async function getDatabaseOverview(deps) {
25414
25460
  const live = deps.store.databasePath;
25415
- const stats = await deps.store.maintenance.stats();
25416
- const settings = await deps.store.config.getSettings();
25461
+ const [stats, tables, settings] = await Promise.all([
25462
+ deps.store.maintenance.stats(),
25463
+ deps.store.maintenance.tables(),
25464
+ deps.store.config.getSettings()
25465
+ ]);
25466
+ const shared = {
25467
+ engine: deps.store.engine,
25468
+ location: live,
25469
+ stats,
25470
+ logicalBytes: stats.pageSize * stats.pageCount,
25471
+ freePageBytes: stats.pageSize * stats.freelistCount,
25472
+ retention: retentionOf(settings),
25473
+ tables
25474
+ };
25475
+ if (deps.store.engine === "postgres") {
25476
+ return {
25477
+ ...shared,
25478
+ fileBytes: 0,
25479
+ walBytes: 0,
25480
+ bodiesBytes: tables.find((table2) => table2.name === "request_bodies")?.bytes ?? 0,
25481
+ freeDiskBytes: null,
25482
+ snapshots: { count: 0, totalBytes: 0, latestAt: null }
25483
+ };
25484
+ }
25417
25485
  const snapshots = listSnapshots(deps);
25418
25486
  return {
25419
- stats,
25487
+ ...shared,
25420
25488
  fileBytes: deps.fs.stat(live)?.size ?? 0,
25421
25489
  walBytes: deps.fs.stat(`${live}-wal`)?.size ?? 0,
25422
25490
  bodiesBytes: deps.fs.dirBytes(bodiesDirFor(live)),
25423
- logicalBytes: stats.pageSize * stats.pageCount,
25424
- freePageBytes: stats.pageSize * stats.freelistCount,
25425
25491
  freeDiskBytes: deps.fs.freeBytes(dirname2(live)),
25426
- retention: retentionOf(settings),
25427
25492
  snapshots: {
25428
25493
  count: snapshots.length,
25429
25494
  totalBytes: snapshots.reduce((sum2, snapshot) => sum2 + snapshot.sizeBytes, 0),
@@ -28447,7 +28512,13 @@ var dbStats = {
28447
28512
  async run(_args, env2) {
28448
28513
  const { ctx, writer } = env2;
28449
28514
  const overview = await getDatabaseOverview(await database(env2));
28450
- emit(ctx, writer, overview, () => fields([
28515
+ const tables = table([{ header: "TABLE" }, { header: "SIZE", align: "right" }, { header: "ROWS", align: "right" }], overview.tables.map((t) => [t.name, formatBytes(t.bytes), String(t.rows)]));
28516
+ const summary = overview.engine === "postgres" ? fields([
28517
+ ["database", overview.location],
28518
+ ["size", formatBytes(overview.logicalBytes)],
28519
+ ["captured bodies", `${formatBytes(overview.bodiesBytes)} (request_bodies table)`],
28520
+ ["schema version", String(overview.stats.schemaVersion)]
28521
+ ]) : fields([
28451
28522
  ["database", ctx.databasePath],
28452
28523
  ["size", formatBytes(overview.fileBytes)],
28453
28524
  ["write-ahead log", formatBytes(overview.walBytes)],
@@ -28466,7 +28537,10 @@ var dbStats = {
28466
28537
  "free disk",
28467
28538
  overview.freeDiskBytes === null ? "unknown" : formatBytes(overview.freeDiskBytes)
28468
28539
  ]
28469
- ]));
28540
+ ]);
28541
+ emit(ctx, writer, overview, () => `${summary}
28542
+
28543
+ ${tables}`);
28470
28544
  }
28471
28545
  };
28472
28546
  var dbSnapshots = {
@@ -28523,6 +28597,21 @@ var dbVacuum = {
28523
28597
  ]));
28524
28598
  }
28525
28599
  };
28600
+ var dbClearBodies = {
28601
+ usage: "db clear-bodies",
28602
+ summary: "Delete every captured request and response body; the requests stay logged",
28603
+ async run(_args, env2) {
28604
+ const { ctx, writer, prompt } = env2;
28605
+ if (!await prompt.confirm("delete every captured body? there is no snapshot of them")) {
28606
+ throw new CliError("cancelled");
28607
+ }
28608
+ const result = await clearBodies(await database(env2));
28609
+ emit(ctx, writer, result, () => fields([
28610
+ ["removed", String(result.removed)],
28611
+ ["orphaned files removed", String(result.orphans)]
28612
+ ]));
28613
+ }
28614
+ };
28526
28615
  function previewTable(preview) {
28527
28616
  const live = preview.live;
28528
28617
  const names = [...new Set([...Object.keys(preview.snapshot), ...Object.keys(live ?? {})])].sort();
@@ -29300,7 +29389,7 @@ function createServiceDeps(input) {
29300
29389
  }
29301
29390
 
29302
29391
  // apps/cli/src/version.ts
29303
- var VERSION = "0.8.0";
29392
+ var VERSION = "0.9.0";
29304
29393
 
29305
29394
  // apps/cli/src/commands/service.ts
29306
29395
  function gatewayArgv(root) {
@@ -29960,7 +30049,8 @@ var COMMANDS = {
29960
30049
  "db snapshots": dbSnapshots,
29961
30050
  "db backup": dbBackup,
29962
30051
  "db restore": dbRestore,
29963
- "db vacuum": dbVacuum
30052
+ "db vacuum": dbVacuum,
30053
+ "db clear-bodies": dbClearBodies
29964
30054
  };
29965
30055
  var VALUED_GLOBALS = new Set(["--root", "--db"]);
29966
30056
  function resolveCommand(argv) {
package/gateway.js CHANGED
@@ -21710,6 +21710,23 @@ function createMaintenanceRepo(sql, nodeId) {
21710
21710
  schemaVersion: row?.version ?? 0
21711
21711
  };
21712
21712
  },
21713
+ async tables() {
21714
+ const rows = await sql.unsafe(`SELECT c.relname AS name,
21715
+ pg_total_relation_size(c.oid) AS bytes,
21716
+ GREATEST(c.reltuples, 0)::bigint AS rows,
21717
+ COALESCE(s.n_dead_tup, 0) AS dead_rows
21718
+ FROM pg_class c
21719
+ JOIN pg_namespace n ON n.oid = c.relnamespace
21720
+ LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid
21721
+ WHERE c.relkind = 'r' AND n.nspname = current_schema()
21722
+ ORDER BY bytes DESC, name`);
21723
+ return rows.map((row) => ({
21724
+ name: row.name,
21725
+ bytes: num(row.bytes),
21726
+ rows: num(row.rows),
21727
+ deadRows: num(row.dead_rows)
21728
+ }));
21729
+ },
21713
21730
  vacuum: async () => unsupported(),
21714
21731
  snapshotTo: async () => unsupported(),
21715
21732
  inspect: async () => unsupported()
@@ -22648,6 +22665,7 @@ async function createPostgresStore(opts) {
22648
22665
  });
22649
22666
  };
22650
22667
  return {
22668
+ engine: "postgres",
22651
22669
  databasePath: redacted(opts.url),
22652
22670
  credentials: createCredentialRepo(sql, opts.encryptionKey, emit),
22653
22671
  config: createConfigRepo(sql, emit),
@@ -23830,6 +23848,15 @@ function createMaintenanceRepo2(db, nodeId) {
23830
23848
  schemaVersion: version2?.version ?? 0
23831
23849
  };
23832
23850
  },
23851
+ async tables() {
23852
+ return db.query(`SELECT m.tbl_name AS name,
23853
+ SUM(s.pgsize) AS bytes,
23854
+ SUM(CASE WHEN s.pagetype = 'leaf' AND s.name = m.tbl_name THEN s.ncell ELSE 0 END) AS rows
23855
+ FROM dbstat s JOIN sqlite_master m ON m.name = s.name
23856
+ WHERE m.tbl_name NOT LIKE 'sqlite_%'
23857
+ GROUP BY m.tbl_name
23858
+ ORDER BY bytes DESC, name`).all().map((row) => ({ ...row, deadRows: null }));
23859
+ },
23833
23860
  async vacuum() {
23834
23861
  db.run("VACUUM");
23835
23862
  db.run("PRAGMA wal_checkpoint(TRUNCATE)");
@@ -24301,6 +24328,7 @@ async function createStore(opts) {
24301
24328
  live = false;
24302
24329
  };
24303
24330
  return {
24331
+ engine: "sqlite",
24304
24332
  databasePath: opts.path,
24305
24333
  credentials: {
24306
24334
  list: () => handle.credentials.list(),
@@ -24365,6 +24393,7 @@ async function createStore(opts) {
24365
24393
  },
24366
24394
  maintenance: {
24367
24395
  stats: () => handle.maintenance.stats(),
24396
+ tables: () => handle.maintenance.tables(),
24368
24397
  heartbeat: (now) => handle.maintenance.heartbeat(now),
24369
24398
  nodes: (now) => handle.maintenance.nodes(now),
24370
24399
  vacuum: () => handle.maintenance.vacuum(),
@@ -26534,13 +26563,13 @@ function encodeSystemTurn(content, cloak) {
26534
26563
  return content.map((block) => encodeBlock(block, cloak));
26535
26564
  }
26536
26565
  function systemCacheControl(req) {
26537
- const cacheable = req.messages.flatMap((message) => message.content.flatMap((block) => block.type === "thinking" ? [] : [{ role: message.role, block }]));
26538
- const markedSystemBlocks = cacheable.filter(({ role, block }) => role === "system" && cacheControlOf(block) !== undefined);
26539
- const final = markedSystemBlocks.at(-1);
26540
- const promoted = final !== undefined && final.block.type === "text" && cacheable.at(-1) === final ? cacheControlOf(final.block) : undefined;
26566
+ const markedSystemBlocks = req.messages.flatMap((message) => message.role === "system" ? message.content.filter((block) => cacheControlOf(block) !== undefined) : []);
26567
+ const trailing = req.messages.at(-1);
26568
+ const marked = trailing?.role === "system" ? trailing.content.findLast((block) => cacheControlOf(block) !== undefined) : undefined;
26569
+ const retarget = marked === undefined ? undefined : cacheControlOf(marked);
26541
26570
  return {
26542
- ...promoted === undefined ? {} : { promoted },
26543
- lost: markedSystemBlocks.length > (promoted === undefined ? 0 : 1)
26571
+ ...retarget === undefined ? {} : { retarget },
26572
+ lost: markedSystemBlocks.length > (retarget === undefined ? 0 : 1)
26544
26573
  };
26545
26574
  }
26546
26575
  function encodeTool(t, cloak) {
@@ -26702,14 +26731,26 @@ function toWire(req, model, opts) {
26702
26731
  return [{ role: m.role, content: encodeSystemTurn(m.content, cloak) }];
26703
26732
  }),
26704
26733
  max_tokens: req.maxTokens ?? 4096,
26705
- stream: req.stream,
26706
- ...systemCache.promoted === undefined ? {} : {
26707
- cache_control: {
26708
- type: systemCache.promoted.type,
26709
- ...systemCache.promoted.ttl === undefined ? {} : { ttl: systemCache.promoted.ttl }
26734
+ stream: req.stream
26735
+ };
26736
+ if (systemCache.retarget !== undefined) {
26737
+ const trailing = body.messages.at(-1);
26738
+ if (isRecord(trailing) && Array.isArray(trailing.content)) {
26739
+ for (const block of trailing.content) {
26740
+ if (isRecord(block))
26741
+ delete block.cache_control;
26710
26742
  }
26711
26743
  }
26712
- };
26744
+ if (!hasVendorCacheControl(req)) {
26745
+ const target = lastCacheableHistoryBlock(body.messages.slice(0, -1));
26746
+ if (target === undefined) {
26747
+ note("anthropic:system-turn-cache-control-dropped");
26748
+ } else if (target.cache_control === undefined) {
26749
+ Object.assign(target, wireCacheControl(systemCache.retarget));
26750
+ note("anthropic:system-turn-cache-control-retargeted");
26751
+ }
26752
+ }
26753
+ }
26713
26754
  if (system !== undefined && system.length > 0)
26714
26755
  body.system = system;
26715
26756
  if (req.temperature !== undefined)
@@ -31010,6 +31051,11 @@ async function importSnapshot(deps, input) {
31010
31051
  }
31011
31052
  });
31012
31053
  }
31054
+ async function clearBodies(deps) {
31055
+ const removed = await deps.store.bodies.prune(deps.now());
31056
+ const orphans = await deps.store.bodies.sweepOrphans();
31057
+ return { removed, orphans };
31058
+ }
31013
31059
  async function vacuum(deps) {
31014
31060
  return withExclusive("a vacuum", async () => {
31015
31061
  const before = deps.fs.stat(deps.store.databasePath)?.size ?? 0;
@@ -31024,18 +31070,37 @@ async function vacuum(deps) {
31024
31070
  }
31025
31071
  async function getDatabaseOverview(deps) {
31026
31072
  const live = deps.store.databasePath;
31027
- const stats = await deps.store.maintenance.stats();
31028
- const settings = await deps.store.config.getSettings();
31073
+ const [stats, tables, settings] = await Promise.all([
31074
+ deps.store.maintenance.stats(),
31075
+ deps.store.maintenance.tables(),
31076
+ deps.store.config.getSettings()
31077
+ ]);
31078
+ const shared = {
31079
+ engine: deps.store.engine,
31080
+ location: live,
31081
+ stats,
31082
+ logicalBytes: stats.pageSize * stats.pageCount,
31083
+ freePageBytes: stats.pageSize * stats.freelistCount,
31084
+ retention: retentionOf(settings),
31085
+ tables
31086
+ };
31087
+ if (deps.store.engine === "postgres") {
31088
+ return {
31089
+ ...shared,
31090
+ fileBytes: 0,
31091
+ walBytes: 0,
31092
+ bodiesBytes: tables.find((table) => table.name === "request_bodies")?.bytes ?? 0,
31093
+ freeDiskBytes: null,
31094
+ snapshots: { count: 0, totalBytes: 0, latestAt: null }
31095
+ };
31096
+ }
31029
31097
  const snapshots = listSnapshots(deps);
31030
31098
  return {
31031
- stats,
31099
+ ...shared,
31032
31100
  fileBytes: deps.fs.stat(live)?.size ?? 0,
31033
31101
  walBytes: deps.fs.stat(`${live}-wal`)?.size ?? 0,
31034
31102
  bodiesBytes: deps.fs.dirBytes(bodiesDirFor(live)),
31035
- logicalBytes: stats.pageSize * stats.pageCount,
31036
- freePageBytes: stats.pageSize * stats.freelistCount,
31037
31103
  freeDiskBytes: deps.fs.freeBytes(dirname2(live)),
31038
- retention: retentionOf(settings),
31039
31104
  snapshots: {
31040
31105
  count: snapshots.length,
31041
31106
  totalBytes: snapshots.reduce((sum2, snapshot) => sum2 + snapshot.sizeBytes, 0),
@@ -48520,15 +48585,27 @@ function adminRoutes(deps) {
48520
48585
  const live = await deps.store.maintenance.nodes(deps.now());
48521
48586
  const reads = await Promise.allSettled(live.map((entry) => fleet.read(entry.id, consoleQuery).then((read) => ({ nodeId: entry.id, read }))));
48522
48587
  const lines = [];
48523
- for (const outcome of reads) {
48524
- if (outcome.status !== "fulfilled")
48588
+ let captured = false;
48589
+ const unreachable2 = [];
48590
+ for (const [index, outcome] of reads.entries()) {
48591
+ if (outcome.status !== "fulfilled") {
48592
+ unreachable2.push(live[index]?.id ?? "");
48525
48593
  continue;
48594
+ }
48595
+ if (outcome.value.read.source !== "none")
48596
+ captured = true;
48526
48597
  for (const line of outcome.value.read.lines) {
48527
48598
  lines.push({ ...line, nodeId: outcome.value.nodeId });
48528
48599
  }
48529
48600
  }
48601
+ if (unreachable2.length === reads.length) {
48602
+ throw new GatewayError("TIMEOUT", "no process in the fleet answered a console read");
48603
+ }
48604
+ const reach = unreachable2.length === 0 ? {} : { unreachable: unreachable2 };
48605
+ if (!captured)
48606
+ return { source: "none", lines: [], ...reach };
48530
48607
  lines.sort((a, b) => (a.at ?? Number.POSITIVE_INFINITY) - (b.at ?? Number.POSITIVE_INFINITY));
48531
- return { source: "fleet", lines: lines.slice(-consoleQuery.lines) };
48608
+ return { source: "fleet", lines: lines.slice(-consoleQuery.lines), ...reach };
48532
48609
  }).get("/api/nodes", async ({ request: request2 }) => {
48533
48610
  await requireReader(request2, deps.admin);
48534
48611
  const nodes = await deps.store.maintenance.nodes(deps.now());
@@ -48701,6 +48778,11 @@ function databaseRoutes(deps) {
48701
48778
  durationMs: result.durationMs
48702
48779
  });
48703
48780
  return { ok: true, ...result };
48781
+ }).delete("/api/database/bodies", async ({ request: request2 }) => {
48782
+ await requireAdmin(request2, deps.admin);
48783
+ const result = await clearBodies(database);
48784
+ logger2.info("captured bodies cleared", { count: result.removed });
48785
+ return { ok: true, ...result };
48704
48786
  }).get("/api/database/snapshots", async ({ request: request2 }) => {
48705
48787
  await requireAdmin(request2, deps.admin);
48706
48788
  return { snapshots: listSnapshots(database) };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnigateway",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
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{m as e}from"./Rack-xBT_6EtM.js";import{jsx as t}from"react/jsx-runtime";import n from"styled-components";var 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=n.span`
1
+ import{m as e}from"./Rack-BbZyyu2u.js";import{jsx as t}from"react/jsx-runtime";import n from"styled-components";var 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=n.span`
2
2
  display: inline-flex;
3
3
  align-items: center;
4
4
  gap: 4px;
@@ -1,4 +1,4 @@
1
- import{C as e,O as t}from"./Panel-CYQvfI3L.js";import{b as n}from"./Rack-xBT_6EtM.js";import{useCallback as r,useEffect as i,useRef as a,useState as o}from"react";import{jsx as s,jsxs as c}from"react/jsx-runtime";import l from"styled-components";var u=n(`check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),d=n(`copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]);function f(e){if(e===null||typeof getSelection!=`function`)return!1;let t=getSelection();if(t===null)return!1;try{let n=document.createRange();return n.selectNodeContents(e),t.removeAllRanges(),t.addRange(n),!0}catch{return!1}}async function p(e,t){if(typeof navigator<`u`&&navigator.clipboard!==void 0)try{return await navigator.clipboard.writeText(e),`copied`}catch{}let n=f(t??null);if(n&&typeof document.execCommand==`function`)try{if(document.execCommand(`copy`))return`copied`}catch{}return n?`selected`:`failed`}var m=l.div`
1
+ import{C as e,O as t}from"./Panel-BlNr9j6-.js";import{b as n}from"./Rack-BbZyyu2u.js";import{useCallback as r,useEffect as i,useRef as a,useState as o}from"react";import{jsx as s,jsxs as c}from"react/jsx-runtime";import l from"styled-components";var u=n(`check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),d=n(`copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]);function f(e){if(e===null||typeof getSelection!=`function`)return!1;let t=getSelection();if(t===null)return!1;try{let n=document.createRange();return n.selectNodeContents(e),t.removeAllRanges(),t.addRange(n),!0}catch{return!1}}async function p(e,t){if(typeof navigator<`u`&&navigator.clipboard!==void 0)try{return await navigator.clipboard.writeText(e),`copied`}catch{}let n=f(t??null);if(n&&typeof document.execCommand==`function`)try{if(document.execCommand(`copy`))return`copied`}catch{}return n?`selected`:`failed`}var m=l.div`
2
2
  display: flex;
3
3
  align-items: stretch;
4
4
  gap: 0;
@@ -1,4 +1,4 @@
1
- import{_ as e}from"./Panel-CYQvfI3L.js";import{useId as t}from"react";import{jsx as n,jsxs as r}from"react/jsx-runtime";import i from"styled-components";var a=`
1
+ import{_ as e}from"./Panel-BlNr9j6-.js";import{useId as t}from"react";import{jsx as n,jsxs as r}from"react/jsx-runtime";import i from"styled-components";var a=`
2
2
  width: 100%;
3
3
  border-radius: 3px;
4
4
  transition: border-color 120ms ease;