omnigateway 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -772,29 +772,26 @@ unknown plugin safer. Integrity checking proves you received the bytes the
772
772
  registry advertised, and nothing about who wrote them or what they do once the
773
773
  gateway imports them.
774
774
 
775
- One ships in this repository:
775
+ None ship in this repository, deliberately. The first one did, and moving it out
776
+ is what proved the plugin API actually works from outside: while it built as a
777
+ workspace sibling it could reach internal packages no published plugin can, and
778
+ two bugs hid in exactly that gap.
776
779
 
777
780
  | Plugin | What it does |
778
781
  | --- | --- |
779
- | [`pokemon`](plugins/pokemon) | A Pokémon companion. Each gateway key raises one that hatches, evolves, and graduates into a Pokédex on the tokens that key spends, with a shop that spends a wallet of those same tokens. |
780
-
781
- It is not installed by default, is not in the npm package, and is not in the
782
- Docker image. Build it from a checkout and install the result:
782
+ | [`omnigateway-plugin-pokemon`](https://github.com/harismawan/omnigateway-plugin-pokemon) | A Pokémon companion. Each gateway key raises one that hatches, evolves, and graduates into a Pokédex on the tokens that key spends, with a shop that spends a wallet of those same tokens. |
783
783
 
784
784
  ```bash
785
- bun run build:plugins # writes plugins/pokemon/dist/pokemon
786
- omni plugin install ./plugins/pokemon/dist/pokemon
785
+ omni plugin install omnigateway-plugin-pokemon
786
+ omni plugin verify pokemon
787
787
  omni restart
788
788
  ```
789
789
 
790
- The path ends in `pokemon` because the installer takes the target directory name
791
- from the source and refuses a manifest whose id disagrees with it — so a plugin
792
- cannot be installed under a name that is not its own.
793
-
794
790
  It needs outbound access to `pokeapi.co` and `raw.githubusercontent.com` for
795
791
  species data and sprites, which its manifest declares and `omni plugin verify
796
- pokemon` prints back. Those assets are Nintendo and Game Freak intellectual property, fetched at
797
- runtime and never vendored into this repository or its published artifacts.
792
+ pokemon` prints back. Those assets are Nintendo and Game Freak intellectual
793
+ property, fetched at runtime and never vendored into that repository, this one,
794
+ or anything either publishes.
798
795
 
799
796
  `verify` is the one to run before restarting a gateway that people are using: it
800
797
  reaches the same verdict the next boot will, from the same code, without loading
@@ -819,14 +816,20 @@ from the path:
819
816
 
820
817
  ```bash
821
818
  # wherever you build — a workstation, CI
822
- bun run build:plugins
823
- tar -czf pokemon.tgz -C plugins/pokemon/dist pokemon
819
+ bun run build # your plugin's own build
820
+ tar -czf my-plugin.tgz -C dist my-plugin
824
821
 
825
822
  # on the host
826
- scp pokemon.tgz gateway-host:/tmp/
827
- ssh gateway-host 'omni plugin install /tmp/pokemon.tgz && omni plugin verify pokemon && omni restart'
823
+ scp my-plugin.tgz gateway-host:/tmp/
824
+ ssh gateway-host 'omni plugin install /tmp/my-plugin.tgz && omni plugin verify my-plugin && omni restart'
828
825
  ```
829
826
 
827
+ Whatever you pack, the manifest must sit at the **root** of the archive once one
828
+ wrapping directory is stripped. A build that nests it — `dist/my-plugin/omni-plugin.json`
829
+ inside a tarball made from the repository root — is refused with "has no
830
+ omni-plugin.json at its root", and that is the most common way a plugin that
831
+ builds fine turns out not to install.
832
+
830
833
  Plaintext `http://` is refused outright and always will be: what arrives over
831
834
  that fetch is code the gateway process will `import`, so anyone between you and
832
835
  the host would be choosing what the gateway runs. Silently upgrading to `https://`
package/bin/omni.js CHANGED
@@ -19991,7 +19991,7 @@ var CORE_TABLES = [
19991
19991
  "usage_rollup",
19992
19992
  "virtual_models"
19993
19993
  ];
19994
- var CORE_TABLE_REFERENCE = new RegExp(`\\b(?:${CORE_TABLES.join("|")})\\b`);
19994
+ var CORE_TABLE_REFERENCE = new RegExp(`\\b(?:${CORE_TABLES.join("|")})\\b`, "i");
19995
19995
  var PREFIX3 = "plugin_";
19996
19996
  function stripNoise(sql) {
19997
19997
  let out = "";
@@ -19999,7 +19999,25 @@ function stripNoise(sql) {
19999
19999
  while (i < sql.length) {
20000
20000
  const c = sql[i];
20001
20001
  const next = sql[i + 1];
20002
- if (c === "'") {
20002
+ if (c === '"' || c === "`" || c === "[") {
20003
+ const close = c === "[" ? "]" : c;
20004
+ out += c;
20005
+ i += 1;
20006
+ while (i < sql.length) {
20007
+ if (sql[i] === close) {
20008
+ if (close !== "]" && sql[i + 1] === close) {
20009
+ out += close + close;
20010
+ i += 2;
20011
+ continue;
20012
+ }
20013
+ break;
20014
+ }
20015
+ out += sql[i];
20016
+ i += 1;
20017
+ }
20018
+ out += close;
20019
+ i += 1;
20020
+ } else if (c === "'") {
20003
20021
  i += 1;
20004
20022
  while (i < sql.length) {
20005
20023
  if (sql[i] === "'") {
@@ -20041,6 +20059,7 @@ function assertPluginId(pluginId) {
20041
20059
  throw new Error(`plugin id ${JSON.stringify(pluginId)} is not a valid identifier`);
20042
20060
  }
20043
20061
  }
20062
+ var CONNECTION_STATEMENT = /^\s*(pragma|attach|detach|vacuum)\b/i;
20044
20063
  function prepare(pluginId, sql) {
20045
20064
  assertPluginId(pluginId);
20046
20065
  const expanded = sql.replace(PLACEHOLDER, (_match, name) => {
@@ -20049,12 +20068,29 @@ function prepare(pluginId, sql) {
20049
20068
  }
20050
20069
  return tableFor(pluginId, name);
20051
20070
  });
20052
- const found = CORE_TABLE_REFERENCE.exec(stripNoise(expanded));
20071
+ const stripped = stripNoise(expanded);
20072
+ const connection = CONNECTION_STATEMENT.exec(stripped);
20073
+ if (connection !== null) {
20074
+ throw new Error(`plugin sql may not ${connection[1]?.toUpperCase()}: the database handle is the gateway's, ` + "and this would reconfigure it for everything sharing it");
20075
+ }
20076
+ const found = CORE_TABLE_REFERENCE.exec(stripped);
20053
20077
  if (found !== null) {
20054
20078
  throw new Error(`plugin sql may not reference the core table ${found[0]}`);
20055
20079
  }
20056
20080
  return expanded;
20057
20081
  }
20082
+ function schemaObjects(db) {
20083
+ const rows = db.query("SELECT name FROM sqlite_master").all();
20084
+ return new Set(rows.map((row) => row.name));
20085
+ }
20086
+ function assertOwnNamespace(db, pluginId, before) {
20087
+ const prefix = `${PREFIX3}${pluginId}_`;
20088
+ for (const name of schemaObjects(db)) {
20089
+ if (before.has(name) || name.startsWith(prefix) || name.startsWith("sqlite_"))
20090
+ continue;
20091
+ throw new Error(`plugin migration created ${name}, which is outside this plugin's namespace \u2014 ` + `name it {{\u2026}} so it becomes ${prefix}\u2026`);
20092
+ }
20093
+ }
20058
20094
  function toBinding(value, index) {
20059
20095
  if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean" || value instanceof Uint8Array) {
20060
20096
  return value;
@@ -20083,8 +20119,10 @@ function createPluginRepo(db) {
20083
20119
  throw new Error("migration version must be a positive integer");
20084
20120
  }
20085
20121
  const sql = prepare(pluginId, migration.sql);
20122
+ const before = schemaObjects(db);
20086
20123
  db.transaction(() => {
20087
20124
  db.run(sql);
20125
+ assertOwnNamespace(db, pluginId, before);
20088
20126
  db.run("INSERT INTO plugin_migrations (plugin_id, version, applied_at) VALUES (?,?,?)", [pluginId, migration.version, Date.now()]);
20089
20127
  })();
20090
20128
  applied.push(migration.version);
package/gateway.js CHANGED
@@ -25465,7 +25465,7 @@ var CORE_TABLES = [
25465
25465
  "usage_rollup",
25466
25466
  "virtual_models"
25467
25467
  ];
25468
- var CORE_TABLE_REFERENCE = new RegExp(`\\b(?:${CORE_TABLES.join("|")})\\b`);
25468
+ var CORE_TABLE_REFERENCE = new RegExp(`\\b(?:${CORE_TABLES.join("|")})\\b`, "i");
25469
25469
  var PREFIX3 = "plugin_";
25470
25470
  function stripNoise(sql) {
25471
25471
  let out = "";
@@ -25473,7 +25473,25 @@ function stripNoise(sql) {
25473
25473
  while (i < sql.length) {
25474
25474
  const c = sql[i];
25475
25475
  const next = sql[i + 1];
25476
- if (c === "'") {
25476
+ if (c === '"' || c === "`" || c === "[") {
25477
+ const close = c === "[" ? "]" : c;
25478
+ out += c;
25479
+ i += 1;
25480
+ while (i < sql.length) {
25481
+ if (sql[i] === close) {
25482
+ if (close !== "]" && sql[i + 1] === close) {
25483
+ out += close + close;
25484
+ i += 2;
25485
+ continue;
25486
+ }
25487
+ break;
25488
+ }
25489
+ out += sql[i];
25490
+ i += 1;
25491
+ }
25492
+ out += close;
25493
+ i += 1;
25494
+ } else if (c === "'") {
25477
25495
  i += 1;
25478
25496
  while (i < sql.length) {
25479
25497
  if (sql[i] === "'") {
@@ -25515,6 +25533,7 @@ function assertPluginId(pluginId) {
25515
25533
  throw new Error(`plugin id ${JSON.stringify(pluginId)} is not a valid identifier`);
25516
25534
  }
25517
25535
  }
25536
+ var CONNECTION_STATEMENT = /^\s*(pragma|attach|detach|vacuum)\b/i;
25518
25537
  function prepare(pluginId, sql) {
25519
25538
  assertPluginId(pluginId);
25520
25539
  const expanded = sql.replace(PLACEHOLDER, (_match, name) => {
@@ -25523,12 +25542,29 @@ function prepare(pluginId, sql) {
25523
25542
  }
25524
25543
  return tableFor(pluginId, name);
25525
25544
  });
25526
- const found = CORE_TABLE_REFERENCE.exec(stripNoise(expanded));
25545
+ const stripped = stripNoise(expanded);
25546
+ const connection = CONNECTION_STATEMENT.exec(stripped);
25547
+ if (connection !== null) {
25548
+ throw new Error(`plugin sql may not ${connection[1]?.toUpperCase()}: the database handle is the gateway's, ` + "and this would reconfigure it for everything sharing it");
25549
+ }
25550
+ const found = CORE_TABLE_REFERENCE.exec(stripped);
25527
25551
  if (found !== null) {
25528
25552
  throw new Error(`plugin sql may not reference the core table ${found[0]}`);
25529
25553
  }
25530
25554
  return expanded;
25531
25555
  }
25556
+ function schemaObjects(db) {
25557
+ const rows = db.query("SELECT name FROM sqlite_master").all();
25558
+ return new Set(rows.map((row) => row.name));
25559
+ }
25560
+ function assertOwnNamespace(db, pluginId, before) {
25561
+ const prefix = `${PREFIX3}${pluginId}_`;
25562
+ for (const name of schemaObjects(db)) {
25563
+ if (before.has(name) || name.startsWith(prefix) || name.startsWith("sqlite_"))
25564
+ continue;
25565
+ throw new Error(`plugin migration created ${name}, which is outside this plugin's namespace \u2014 ` + `name it {{\u2026}} so it becomes ${prefix}\u2026`);
25566
+ }
25567
+ }
25532
25568
  function toBinding(value, index) {
25533
25569
  if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean" || value instanceof Uint8Array) {
25534
25570
  return value;
@@ -25557,8 +25593,10 @@ function createPluginRepo(db) {
25557
25593
  throw new Error("migration version must be a positive integer");
25558
25594
  }
25559
25595
  const sql = prepare(pluginId, migration.sql);
25596
+ const before = schemaObjects(db);
25560
25597
  db.transaction(() => {
25561
25598
  db.run(sql);
25599
+ assertOwnNamespace(db, pluginId, before);
25562
25600
  db.run("INSERT INTO plugin_migrations (plugin_id, version, applied_at) VALUES (?,?,?)", [pluginId, migration.version, Date.now()]);
25563
25601
  })();
25564
25602
  applied.push(migration.version);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnigateway",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
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>",