omnigateway 0.1.1 → 0.1.2

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/bin/omni.js CHANGED
@@ -16415,25 +16415,104 @@ function requireDimension(grain, dimension) {
16415
16415
  }
16416
16416
 
16417
16417
  // packages/control/src/credentials.ts
16418
+ function summarizeCredential(credential) {
16419
+ return {
16420
+ id: credential.id,
16421
+ provider: credential.provider,
16422
+ label: credential.label,
16423
+ authType: credential.authType,
16424
+ enabled: credential.enabled,
16425
+ tier: credential.tier,
16426
+ weight: credential.weight,
16427
+ expiresAt: credential.expiresAt,
16428
+ accountEmail: credential.accountEmail,
16429
+ providerData: credential.providerData,
16430
+ disabledReason: credential.disabledReason,
16431
+ disabledAt: credential.disabledAt,
16432
+ hasRefreshToken: credential.hasRefreshToken,
16433
+ createdAt: credential.createdAt,
16434
+ updatedAt: credential.updatedAt
16435
+ };
16436
+ }
16418
16437
  async function listCredentials(store) {
16419
- const credentials = await store.credentials.list();
16420
- return credentials.map((c) => ({
16421
- id: c.id,
16422
- provider: c.provider,
16423
- label: c.label,
16424
- authType: c.authType,
16425
- enabled: c.enabled,
16426
- tier: c.tier,
16427
- weight: c.weight,
16428
- expiresAt: c.expiresAt,
16429
- accountEmail: c.accountEmail,
16430
- providerData: c.providerData,
16431
- disabledReason: c.disabledReason,
16432
- disabledAt: c.disabledAt,
16433
- hasRefreshToken: c.hasRefreshToken,
16434
- createdAt: c.createdAt,
16435
- updatedAt: c.updatedAt
16436
- }));
16438
+ return (await store.credentials.list()).map(summarizeCredential);
16439
+ }
16440
+ async function getCredential(store, id) {
16441
+ const credential = await store.credentials.get(id);
16442
+ if (credential === null)
16443
+ throw new GatewayError("BAD_REQUEST", "no such credential");
16444
+ return summarizeCredential(credential);
16445
+ }
16446
+ async function createApiKeyCredential(store, input) {
16447
+ const provider = parseOrThrow(providerIdSchema, input.provider);
16448
+ if (typeof input.apiKey !== "string" || input.apiKey.trim().length === 0) {
16449
+ throw new GatewayError("BAD_REQUEST", "apiKey: must not be empty");
16450
+ }
16451
+ if (input.label !== undefined && typeof input.label !== "string") {
16452
+ throw new GatewayError("BAD_REQUEST", "label: must be a string");
16453
+ }
16454
+ const label = input.label?.trim() || `${provider} api key`;
16455
+ const created = await store.credentials.create({
16456
+ id: crypto.randomUUID(),
16457
+ provider,
16458
+ label,
16459
+ authType: "apiKey",
16460
+ enabled: true,
16461
+ tier: 1,
16462
+ weight: 1,
16463
+ expiresAt: null,
16464
+ accountEmail: null,
16465
+ providerData: {},
16466
+ disabledReason: null,
16467
+ disabledAt: null,
16468
+ accessToken: null,
16469
+ refreshToken: null,
16470
+ apiKey: input.apiKey,
16471
+ idToken: null
16472
+ });
16473
+ return summarizeCredential(created);
16474
+ }
16475
+ async function refreshCredential(deps, id) {
16476
+ const credential = await deps.store.credentials.get(id);
16477
+ if (credential === null)
16478
+ throw new GatewayError("BAD_REQUEST", "no such credential");
16479
+ if (credential.authType !== "oauth") {
16480
+ throw new GatewayError("BAD_REQUEST", `credential "${id}" is an api key and has nothing to refresh`);
16481
+ }
16482
+ await deps.refresh(credential);
16483
+ return getCredential(deps.store, id);
16484
+ }
16485
+ async function credentialHealth(store) {
16486
+ const [health, quota] = await Promise.all([
16487
+ store.credentials.listHealth(),
16488
+ store.credentials.listQuota()
16489
+ ]);
16490
+ return { health, quota };
16491
+ }
16492
+ async function credentialStatus(store, options) {
16493
+ const [credentials, quota, adminConfigured] = await Promise.all([
16494
+ listCredentials(store),
16495
+ store.credentials.listQuota(),
16496
+ createAdminAuth(store, { now: options.now, sessionTtlMs: 0 }).isConfigured()
16497
+ ]);
16498
+ const byCredential = new Map;
16499
+ for (const row of quota) {
16500
+ const rows = byCredential.get(row.credentialId);
16501
+ if (rows === undefined)
16502
+ byCredential.set(row.credentialId, [row]);
16503
+ else
16504
+ rows.push(row);
16505
+ }
16506
+ return {
16507
+ adminConfigured,
16508
+ credentials: credentials.map(({ id, provider, label, enabled }) => ({
16509
+ id,
16510
+ provider,
16511
+ label,
16512
+ enabled,
16513
+ quota: byCredential.get(id) ?? []
16514
+ }))
16515
+ };
16437
16516
  }
16438
16517
  async function patchCredential(deps, id, input) {
16439
16518
  const patch = parseOrThrow(credentialPatchSchema, input);
@@ -16461,7 +16540,7 @@ function healthKey(credentialId, model) {
16461
16540
  }
16462
16541
  async function buildSnapshot(store, now) {
16463
16542
  const [credentials, healthRows, quotaRows, models, settings] = await Promise.all([
16464
- store.credentials.list(),
16543
+ store.credentials.listRouting(),
16465
16544
  store.credentials.listHealth(),
16466
16545
  store.credentials.listQuota(),
16467
16546
  store.config.listModels(),
@@ -16775,7 +16854,7 @@ var DEFAULT_SETTINGS = {
16775
16854
  // packages/store/src/sqlite/config.ts
16776
16855
  var SETTINGS_KEY = "settings";
16777
16856
  var ADMIN_HASH_KEY = "adminPasswordHash";
16778
- function createConfigRepo(db) {
16857
+ function createConfigRepo(db, emit2 = () => {}) {
16779
16858
  const readRaw = (key) => db.query("SELECT value FROM settings WHERE key = ?").get(key)?.value ?? null;
16780
16859
  const writeRaw = (key, value) => {
16781
16860
  db.run("INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value", [key, value]);
@@ -16795,9 +16874,11 @@ function createConfigRepo(db) {
16795
16874
  targets = excluded.targets,
16796
16875
  strategy = excluded.strategy,
16797
16876
  is_alias = excluded.is_alias`, [model.id, JSON.stringify(model.targets), model.strategy, model.isAlias ? 1 : 0]);
16877
+ emit2({ type: "modelsChanged" });
16798
16878
  },
16799
16879
  async removeModel(id) {
16800
16880
  db.run("DELETE FROM virtual_models WHERE id = ?", [id]);
16881
+ emit2({ type: "modelsChanged" });
16801
16882
  },
16802
16883
  async getSettings() {
16803
16884
  const raw = readRaw(SETTINGS_KEY);
@@ -16818,6 +16899,7 @@ function createConfigRepo(db) {
16818
16899
  weights: { ...current.weights, ...patch.weights }
16819
16900
  };
16820
16901
  writeRaw(SETTINGS_KEY, JSON.stringify(next));
16902
+ emit2({ type: "settingsChanged" });
16821
16903
  return next;
16822
16904
  },
16823
16905
  async getAdminPasswordHash() {
@@ -16833,8 +16915,40 @@ function createConfigRepo(db) {
16833
16915
  };
16834
16916
  }
16835
16917
  // packages/store/src/sqlite/credentials.ts
16836
- function createCredentialRepo(db, key) {
16837
- const view = (row) => ({
16918
+ function createCredentialRepo(db, key, emit2 = () => {}) {
16919
+ const open = async (v) => v === null ? null : decrypt(key, v);
16920
+ const secretsFrom = async (row) => ({
16921
+ accessToken: await open(row.access_token),
16922
+ refreshToken: await open(row.refresh_token),
16923
+ apiKey: await open(row.api_key),
16924
+ idToken: await open(row.id_token)
16925
+ });
16926
+ const requiredRow = (row, id) => {
16927
+ if (row === null)
16928
+ throw new Error(`credential ${id} no longer exists`);
16929
+ return row;
16930
+ };
16931
+ const currentSecrets = async (id) => {
16932
+ const row = requiredRow(db.query("SELECT access_token, refresh_token, api_key, id_token FROM credentials WHERE id = ?").get(id), id);
16933
+ return secretsFrom(row);
16934
+ };
16935
+ const currentInferenceSecrets = async (id, authType) => {
16936
+ if (authType === "oauth") {
16937
+ const row2 = requiredRow(db.query("SELECT access_token FROM credentials WHERE id = ?").get(id), id);
16938
+ return { accessToken: await open(row2.access_token), apiKey: null };
16939
+ }
16940
+ const row = requiredRow(db.query("SELECT api_key FROM credentials WHERE id = ?").get(id), id);
16941
+ return { accessToken: null, apiKey: await open(row.api_key) };
16942
+ };
16943
+ const currentRefreshSecrets = async (id) => {
16944
+ const row = requiredRow(db.query("SELECT refresh_token FROM credentials WHERE id = ?").get(id), id);
16945
+ return { refreshToken: await open(row.refresh_token) };
16946
+ };
16947
+ const currentUsageSecrets = async (id) => {
16948
+ const row = requiredRow(db.query("SELECT access_token FROM credentials WHERE id = ?").get(id), id);
16949
+ return { accessToken: await open(row.access_token) };
16950
+ };
16951
+ const view = (row, loadCurrentSecrets = false) => ({
16838
16952
  id: row.id,
16839
16953
  provider: row.provider,
16840
16954
  label: row.label,
@@ -16850,18 +16964,31 @@ function createCredentialRepo(db, key) {
16850
16964
  hasRefreshToken: row.refresh_token !== null,
16851
16965
  createdAt: row.created_at,
16852
16966
  updatedAt: row.updated_at,
16853
- secrets: async () => ({
16854
- accessToken: await open(row.access_token),
16855
- refreshToken: await open(row.refresh_token),
16856
- apiKey: await open(row.api_key),
16857
- idToken: await open(row.id_token)
16858
- })
16967
+ secrets: () => loadCurrentSecrets ? currentSecrets(row.id) : secretsFrom(row),
16968
+ openForInference: async () => {
16969
+ if (loadCurrentSecrets)
16970
+ return currentInferenceSecrets(row.id, row.auth_type);
16971
+ if (row.auth_type === "oauth") {
16972
+ return { accessToken: await open(row.access_token), apiKey: null };
16973
+ }
16974
+ return { accessToken: null, apiKey: await open(row.api_key) };
16975
+ },
16976
+ openForRefresh: async () => loadCurrentSecrets ? currentRefreshSecrets(row.id) : { refreshToken: await open(row.refresh_token) },
16977
+ openForUsage: async () => loadCurrentSecrets ? currentUsageSecrets(row.id) : { accessToken: await open(row.access_token) }
16859
16978
  });
16860
- const open = async (v) => v === null ? null : decrypt(key, v);
16861
16979
  const seal = async (v) => v === null || v === undefined ? null : encrypt(key, v);
16862
16980
  return {
16863
16981
  async list() {
16864
- return db.query("SELECT * FROM credentials ORDER BY tier, label").all().map(view);
16982
+ return db.query("SELECT * FROM credentials ORDER BY tier, label").all().map((row) => view(row));
16983
+ },
16984
+ async listRouting() {
16985
+ return db.query(`SELECT id, provider, label, auth_type, enabled, tier, weight, expires_at,
16986
+ account_email, provider_data, disabled_reason, disabled_at,
16987
+ NULL AS access_token,
16988
+ CASE WHEN refresh_token IS NULL THEN NULL ELSE 'present' END AS refresh_token,
16989
+ NULL AS api_key, NULL AS id_token, created_at, updated_at
16990
+ FROM credentials
16991
+ ORDER BY tier, label`).all().map((row) => view(row, true));
16865
16992
  },
16866
16993
  async get(id) {
16867
16994
  const row = db.query("SELECT * FROM credentials WHERE id = ?").get(id);
@@ -16894,6 +17021,7 @@ function createCredentialRepo(db, key) {
16894
17021
  now
16895
17022
  ]);
16896
17023
  const { accessToken, refreshToken, apiKey, idToken, ...meta3 } = input;
17024
+ emit2({ type: "credentialsChanged" });
16897
17025
  return {
16898
17026
  ...meta3,
16899
17027
  hasRefreshToken: refreshToken != null,
@@ -16930,6 +17058,7 @@ function createCredentialRepo(db, key) {
16930
17058
  return;
16931
17059
  put("updated_at", Date.now());
16932
17060
  db.run(`UPDATE credentials SET ${sets.join(", ")} WHERE id = ?`, [...vals, id]);
17061
+ emit2({ type: "credentialsChanged" });
16933
17062
  },
16934
17063
  async updateSecrets(id, secrets, expiresAt) {
16935
17064
  const sets = [];
@@ -16953,9 +17082,11 @@ function createCredentialRepo(db, key) {
16953
17082
  sets.push("expires_at = ?", "updated_at = ?");
16954
17083
  vals.push(expiresAt, Date.now());
16955
17084
  db.run(`UPDATE credentials SET ${sets.join(", ")} WHERE id = ?`, [...vals, id]);
17085
+ emit2({ type: "credentialsChanged" });
16956
17086
  },
16957
17087
  async remove(id) {
16958
17088
  db.run("DELETE FROM credentials WHERE id = ?", [id]);
17089
+ emit2({ type: "credentialsChanged" });
16959
17090
  },
16960
17091
  async listHealth() {
16961
17092
  return db.query("SELECT * FROM credential_health").all().map((r) => ({
@@ -16986,6 +17117,7 @@ function createCredentialRepo(db, key) {
16986
17117
  stmt.run(r.credentialId, r.model, r.breakerState, r.consecutiveFailures, r.openedAt, r.rateLimitedUntil, r.ewmaTtftMs, r.lastUsedAt);
16987
17118
  }
16988
17119
  })();
17120
+ emit2({ type: "healthSaved", rows });
16989
17121
  },
16990
17122
  async listQuota() {
16991
17123
  return db.query("SELECT * FROM quota_windows").all().map((r) => ({
@@ -17025,6 +17157,7 @@ function createCredentialRepo(db, key) {
17025
17157
  prune.run(credentialId, JSON.stringify(types2));
17026
17158
  }
17027
17159
  })();
17160
+ emit2({ type: "quotaSaved", rows });
17028
17161
  }
17029
17162
  };
17030
17163
  }
@@ -17497,11 +17630,26 @@ function createUsageRepo(db) {
17497
17630
  // packages/store/src/sqlite/store.ts
17498
17631
  async function createStore(opts) {
17499
17632
  const db = openDb(opts.path);
17633
+ const listeners = new Set;
17634
+ const emit2 = (change) => {
17635
+ for (const listener of listeners) {
17636
+ try {
17637
+ listener(change);
17638
+ } catch {}
17639
+ }
17640
+ };
17500
17641
  return {
17501
- credentials: createCredentialRepo(db, opts.encryptionKey),
17502
- config: createConfigRepo(db),
17642
+ credentials: createCredentialRepo(db, opts.encryptionKey, emit2),
17643
+ config: createConfigRepo(db, emit2),
17503
17644
  keys: createKeyRepo(db),
17504
17645
  usage: createUsageRepo(db),
17646
+ routing: {
17647
+ version: () => db.query("PRAGMA data_version").get()?.data_version ?? 0,
17648
+ subscribe(listener) {
17649
+ listeners.add(listener);
17650
+ return () => listeners.delete(listener);
17651
+ }
17652
+ },
17505
17653
  close: () => db.close()
17506
17654
  };
17507
17655
  }
@@ -17829,7 +17977,7 @@ var OAUTH_PROVIDERS = {
17829
17977
  function createRefresher(deps) {
17830
17978
  const inFlight = new Map;
17831
17979
  async function run(credential) {
17832
- const secrets = await credential.secrets();
17980
+ const secrets = await credential.openForRefresh();
17833
17981
  if (secrets.refreshToken === null) {
17834
17982
  throw new GatewayError("AUTH", `credential ${credential.id} has no refresh token`);
17835
17983
  }
@@ -18166,10 +18314,7 @@ var credentialsList = {
18166
18314
  }
18167
18315
  };
18168
18316
  async function findCredential(ctx, id) {
18169
- const credential = await (await ctx.store()).credentials.get(id) ?? null;
18170
- if (credential === null)
18171
- throw new CliError(`no credential "${id}"`);
18172
- return credential;
18317
+ return getCredential(await ctx.store(), id);
18173
18318
  }
18174
18319
  var credentialsShow = {
18175
18320
  usage: "credentials show <id>",
@@ -18268,12 +18413,7 @@ var credentialsRefresh = {
18268
18413
  async run(args, { ctx, writer }) {
18269
18414
  const id = requirePositional(args, 0, "credential id");
18270
18415
  const store = await ctx.store();
18271
- const credential = await store.credentials.get(id);
18272
- if (credential === null)
18273
- throw new CliError(`no credential "${id}"`);
18274
- if (credential.authType !== "oauth") {
18275
- throw new CliError(`credential "${id}" is an api key and has nothing to refresh`);
18276
- }
18416
+ const credential = await findCredential(ctx, id);
18277
18417
  const refresh = createRefresher({
18278
18418
  store,
18279
18419
  providers: OAUTH_PROVIDERS,
@@ -18281,9 +18421,8 @@ var credentialsRefresh = {
18281
18421
  now: ctx.now
18282
18422
  });
18283
18423
  note(ctx, writer, `refreshing ${credential.provider} credential ${id}\u2026`);
18284
- await refresh(credential);
18285
- const updated = await store.credentials.get(id);
18286
- emit(ctx, writer, { id, expiresAt: updated?.expiresAt ?? null }, () => `${id} refreshed; expires ${formatTime(updated?.expiresAt ?? null)}`);
18424
+ const updated = await refreshCredential({ store, refresh }, id);
18425
+ emit(ctx, writer, { id, expiresAt: updated.expiresAt }, () => `${id} refreshed; expires ${formatTime(updated.expiresAt)}`);
18287
18426
  }
18288
18427
  };
18289
18428
  var credentialsAddKey = {
@@ -18298,27 +18437,12 @@ var credentialsAddKey = {
18298
18437
  const key = await prompt.secret(`${providerId} API key: `);
18299
18438
  if (key.length === 0)
18300
18439
  throw new CliError("no API key given");
18301
- const store = await ctx.store();
18302
- const id = crypto.randomUUID();
18303
- await store.credentials.create({
18304
- id,
18440
+ const created = await createApiKeyCredential(await ctx.store(), {
18305
18441
  provider: providerId,
18306
- label: stringFlag(args.values, "label") ?? `${providerId} api key`,
18307
- authType: "apiKey",
18308
- enabled: true,
18309
- tier: 1,
18310
- weight: 1,
18311
- expiresAt: null,
18312
- accountEmail: null,
18313
- providerData: {},
18314
- disabledReason: null,
18315
- disabledAt: null,
18316
- accessToken: null,
18317
- refreshToken: null,
18318
18442
  apiKey: key,
18319
- idToken: null
18443
+ label: stringFlag(args.values, "label")
18320
18444
  });
18321
- emit(ctx, writer, { id, provider: providerId }, () => `stored ${providerId} api key as ${id}`);
18445
+ emit(ctx, writer, { id: created.id, provider: created.provider }, () => `stored ${created.provider} api key as ${created.id}`);
18322
18446
  }
18323
18447
  };
18324
18448
  var credentialsHealth = {
@@ -18327,8 +18451,8 @@ var credentialsHealth = {
18327
18451
  options: { all: { type: "boolean" } },
18328
18452
  async run(args, { ctx, writer }) {
18329
18453
  const store = await ctx.store();
18330
- const [rows, credentials] = await Promise.all([
18331
- store.credentials.listHealth(),
18454
+ const [{ health: rows }, credentials] = await Promise.all([
18455
+ credentialHealth(store),
18332
18456
  listCredentials(store)
18333
18457
  ]);
18334
18458
  const labels = new Map(credentials.map((c) => [c.id, c.label]));
@@ -19167,30 +19291,11 @@ var status2 = {
19167
19291
  } catch (error51) {
19168
19292
  storeError = error51 instanceof Error ? error51.message : "could not open the database";
19169
19293
  }
19170
- const credentials = store === null ? [] : await store.credentials.list();
19171
- const quotaRows = store === null ? [] : await store.credentials.listQuota();
19172
- const configured = store === null ? false : await createAdminAuth(store, {
19173
- now: ctx.now,
19174
- sessionTtlMs: 0
19175
- }).isConfigured();
19176
- const byCredential = new Map;
19177
- for (const row of quotaRows) {
19178
- const list = byCredential.get(row.credentialId);
19179
- if (list === undefined)
19180
- byCredential.set(row.credentialId, [row]);
19181
- else
19182
- list.push(row);
19183
- }
19294
+ const persistent = store === null ? { adminConfigured: false, credentials: [] } : await credentialStatus(store, { now: ctx.now });
19295
+ const { adminConfigured: configured, credentials } = persistent;
19184
19296
  const data = {
19185
19297
  process: process3,
19186
- adminConfigured: configured,
19187
- credentials: credentials.map((credential) => ({
19188
- id: credential.id,
19189
- provider: credential.provider,
19190
- label: credential.label,
19191
- enabled: credential.enabled,
19192
- quota: byCredential.get(credential.id) ?? []
19193
- })),
19298
+ ...persistent,
19194
19299
  storeError
19195
19300
  };
19196
19301
  emit(ctx, writer, data, () => {
@@ -19212,15 +19317,12 @@ ${state(ctx, false, storeError)}`;
19212
19317
 
19213
19318
  no credentials; add one with: omni connect <provider>`;
19214
19319
  }
19215
- const rows = credentials.map((credential) => {
19216
- const windows = byCredential.get(credential.id) ?? [];
19217
- return [
19218
- credential.label,
19219
- provider(ctx, credential.provider),
19220
- state(ctx, credential.enabled, credential.enabled ? "enabled" : "disabled"),
19221
- quotaCell(ctx, windows, ctx.now())
19222
- ];
19223
- });
19320
+ const rows = credentials.map((credential) => [
19321
+ credential.label,
19322
+ provider(ctx, credential.provider),
19323
+ state(ctx, credential.enabled, credential.enabled ? "enabled" : "disabled"),
19324
+ quotaCell(ctx, credential.quota, ctx.now())
19325
+ ]);
19224
19326
  return `${header}
19225
19327
 
19226
19328
  ${table([{ header: "ACCOUNT" }, { header: "PROVIDER" }, { header: "STATE" }, { header: "QUOTA" }], rows)}`;