omnigateway 0.1.7 → 0.1.8

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 (35) hide show
  1. package/bin/omni.js +351 -124
  2. package/gateway.js +402 -182
  3. package/package.json +1 -1
  4. package/public/assets/{Chip-C69u2U_b.js → Chip-CM9ZMdr9.js} +1 -1
  5. package/public/assets/{CopyValue-CJy2T0H6.js → CopyValue-WrOTcHwq.js} +4 -4
  6. package/public/assets/{Field-Sc8mDqJv.js → Field-Br6jKPz8.js} +8 -8
  7. package/public/assets/{Lamp-7jD_9929.js → Lamp-DO-sRDg2.js} +7 -7
  8. package/public/assets/{Meter-C0nUftAa.js → Meter-BzMaum0C.js} +1 -1
  9. package/public/assets/{Modal-CiVHSJmm.js → Modal-lhjr949H.js} +8 -8
  10. package/public/assets/{Rack-CMqGBP4I.js → Rack-BFWC50ex.js} +18 -18
  11. package/public/assets/{Readout-Dv2dZWtI.js → Readout-DMJs4gYD.js} +5 -5
  12. package/public/assets/{States-D55MhqMx.js → States-BNeCLhZn.js} +10 -10
  13. package/public/assets/{Table-DBB3jdgi.js → Table-nOg_lgEJ.js} +1 -1
  14. package/public/assets/{Toggle-BoGjb5oi.js → Toggle-DYoCVD37.js} +2 -2
  15. package/public/assets/_app-lXr06Xli.js +1 -0
  16. package/public/assets/_app.accounts-BTb5vHI_.js +51 -0
  17. package/public/assets/{_app.console-Bka7AkiL.js → _app.console-DHUClMsI.js} +7 -7
  18. package/public/assets/_app.index-DZ--9mKU.js +61 -0
  19. package/public/assets/{_app.keys-BX8k8COP.js → _app.keys-B-cy-2DS.js} +8 -8
  20. package/public/assets/_app.logs-BQjAIT2P.js +33 -0
  21. package/public/assets/_app.models-BAmXHlf2.js +144 -0
  22. package/public/assets/_app.settings-CG8bv_rm.js +34 -0
  23. package/public/assets/{_app.usage-BRhEvwVr.js → _app.usage-BLrQ2hB8.js} +28 -28
  24. package/public/assets/index-UAZ0O5y0.js +170 -0
  25. package/public/assets/{login-DaC-wVpI.js → login-BpUl6C5b.js} +8 -8
  26. package/public/assets/{queries-2E6-CboE.js → queries-D_-Jj9vt.js} +1 -1
  27. package/public/assets/{trash-2--aH2iKg-.js → trash-2-DjWP2u19.js} +1 -1
  28. package/public/index.html +2 -2
  29. package/public/assets/_app-p5Mz7sXD.js +0 -1
  30. package/public/assets/_app.accounts-B5wMO9TH.js +0 -51
  31. package/public/assets/_app.index-MnQuadXb.js +0 -61
  32. package/public/assets/_app.logs-DD-sd4fn.js +0 -33
  33. package/public/assets/_app.models-CezFNL6y.js +0 -144
  34. package/public/assets/_app.settings-D1taAt8B.js +0 -16
  35. package/public/assets/index-vCuK1fRQ.js +0 -170
package/bin/omni.js CHANGED
@@ -90,6 +90,11 @@ function formatUsd(value) {
90
90
  return `$${value.toFixed(value < 1 ? 4 : 2)}`;
91
91
  }
92
92
 
93
+ // apps/cli/src/run.ts
94
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
95
+ import { homedir as homedir3 } from "os";
96
+ import { dirname as dirname3 } from "path";
97
+
93
98
  // packages/control/src/adminAuth.ts
94
99
  import { hash, verify } from "@node-rs/argon2";
95
100
  var MIN_PASSWORD_LENGTH = 12;
@@ -233,6 +238,7 @@ function usageFromPromptTotal(promptTokens, outputTokens, cacheReadTokens, cache
233
238
  }
234
239
  // packages/control/src/config.ts
235
240
  var MIN_KEY_LENGTH = 16;
241
+ var TRUTHY = new Set(["1", "true", "yes", "on"]);
236
242
  var DECIMAL_INTEGER = /^\d+$/;
237
243
  function optionalText(value, fallback) {
238
244
  return value?.trim() || fallback;
@@ -252,6 +258,7 @@ function loadConfig(env) {
252
258
  const baseUrl = optionalText(env.OMNI_BASE_URL, derivedBaseUrl).replace(/\/+$/, "") || derivedBaseUrl;
253
259
  const staticDir = env.OMNI_STATIC_DIR?.trim();
254
260
  const logFile = env.OMNI_LOG_FILE?.trim();
261
+ const exposeClaudeCodeAliases = TRUTHY.has((env.OMNI_EXPOSE_CLAUDE_CODE_ALIASES ?? "").trim().toLowerCase());
255
262
  const rawLogLevel = env.OMNI_LOG_LEVEL?.trim();
256
263
  const logLevel = parseLogLevel(rawLogLevel);
257
264
  return {
@@ -263,9 +270,14 @@ function loadConfig(env) {
263
270
  encryptionKey,
264
271
  baseUrl,
265
272
  staticDir: staticDir === undefined || staticDir.length === 0 ? null : staticDir,
273
+ exposeClaudeCodeAliases,
266
274
  logFile: logFile === undefined || logFile.length === 0 ? null : logFile
267
275
  };
268
276
  }
277
+ // packages/providers/src/betas.ts
278
+ var CONTEXT_1M_BETA = "context-1m-2025-08-07";
279
+ var CONTEXT_1M_TOKENS = 1e6;
280
+
269
281
  // packages/providers/src/body.ts
270
282
  var BODY_ORDER = {
271
283
  anthropic: [
@@ -373,6 +385,119 @@ function signAnthropicBody(json) {
373
385
  return json.replace(needle, `cch=${computeCch(json)};`);
374
386
  }
375
387
 
388
+ // packages/providers/src/anthropic/models.ts
389
+ var ANTHROPIC_MODELS = {
390
+ defaultModel: "claude-opus-5",
391
+ models: [
392
+ {
393
+ id: "claude-fable-5",
394
+ label: "Claude Fable 5",
395
+ pricing: { input: 10, output: 50, cacheRead: 1, cacheWrite5m: 12.5, cacheWrite1h: 20 },
396
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
397
+ },
398
+ {
399
+ id: "claude-opus-5",
400
+ label: "Claude Opus 5",
401
+ pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite5m: 6.25, cacheWrite1h: 10 },
402
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
403
+ },
404
+ {
405
+ id: "claude-sonnet-5",
406
+ label: "Claude Sonnet 5",
407
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 3.75, cacheWrite1h: 6 },
408
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
409
+ },
410
+ {
411
+ id: "claude-haiku-4-5",
412
+ label: "Claude Haiku 4.5",
413
+ pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
414
+ limits: { contextWindow: 200000, maxOutputTokens: 64000 }
415
+ }
416
+ ]
417
+ };
418
+
419
+ // packages/providers/src/kimi/models.ts
420
+ var KIMI_MODELS = {
421
+ defaultModel: "k3-256k",
422
+ models: [
423
+ {
424
+ id: "k3-256k",
425
+ label: "Kimi K3 \u2014 256K",
426
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
427
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
428
+ },
429
+ {
430
+ id: "k3",
431
+ label: "Kimi K3 \u2014 up to 1M",
432
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
433
+ limits: { contextWindow: 1048576, maxOutputTokens: 131072 }
434
+ },
435
+ {
436
+ id: "kimi-for-coding",
437
+ label: "Kimi K2.7 Code",
438
+ pricing: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
439
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
440
+ },
441
+ {
442
+ id: "kimi-for-coding-highspeed",
443
+ label: "Kimi K2.7 Code \u2014 High Speed",
444
+ pricing: { input: 0.95, output: 8, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
445
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
446
+ }
447
+ ]
448
+ };
449
+
450
+ // packages/providers/src/openai/models.ts
451
+ var OPENAI_MODELS = {
452
+ defaultModel: "gpt-5.6",
453
+ models: [
454
+ {
455
+ id: "gpt-5.6",
456
+ label: "GPT-5.6 \u2014 routes to Sol",
457
+ pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
458
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
459
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
460
+ },
461
+ {
462
+ id: "gpt-5.6-sol",
463
+ label: "GPT-5.6 Sol \u2014 deepest reasoning",
464
+ pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
465
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
466
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
467
+ },
468
+ {
469
+ id: "gpt-5.6-terra",
470
+ label: "GPT-5.6 Terra \u2014 balanced",
471
+ pricing: { input: 2, output: 12, cacheRead: 0.2, cacheWrite5m: 0, cacheWrite1h: 0 },
472
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
473
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
474
+ },
475
+ {
476
+ id: "gpt-5.6-luna",
477
+ label: "GPT-5.6 Luna \u2014 fastest",
478
+ pricing: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite5m: 0, cacheWrite1h: 0 },
479
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
480
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
481
+ }
482
+ ]
483
+ };
484
+
485
+ // packages/providers/src/catalog.ts
486
+ var PROVIDER_MODEL_CATALOG = {
487
+ anthropic: ANTHROPIC_MODELS,
488
+ openai: OPENAI_MODELS,
489
+ kimi: KIMI_MODELS
490
+ };
491
+ function catalogPricing(provider, model) {
492
+ return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
493
+ }
494
+ function catalogLimits(provider, model, auth = "apiKey") {
495
+ const entry = PROVIDER_MODEL_CATALOG[provider]?.models.find((choice) => choice.id === model);
496
+ if (entry === undefined)
497
+ return null;
498
+ return (auth === "oauth" ? entry.oauthLimits : undefined) ?? entry.limits;
499
+ }
500
+
376
501
  // packages/providers/src/http.ts
377
502
  function codeForStatus(status) {
378
503
  if (status === 401 || status === 403)
@@ -930,6 +1055,15 @@ var anthropicAdapter = {
930
1055
  ["Accept", "text/event-stream"]
931
1056
  ];
932
1057
  const betas = new Set(req.request.betas ?? []);
1058
+ const notes = [...degradations];
1059
+ if (betas.has(CONTEXT_1M_BETA)) {
1060
+ const limits = catalogLimits("anthropic", req.model, oauth ? "oauth" : "apiKey");
1061
+ const window = limits?.contextWindow;
1062
+ if (window !== undefined && window < CONTEXT_1M_TOKENS) {
1063
+ betas.delete(CONTEXT_1M_BETA);
1064
+ notes.push("anthropic:context-1m-dropped");
1065
+ }
1066
+ }
933
1067
  if (oauth) {
934
1068
  protocol.push(["Authorization", `Bearer ${req.credentials.accessToken}`]);
935
1069
  betas.add(OAUTH_BETA);
@@ -957,121 +1091,9 @@ var anthropicAdapter = {
957
1091
  throw await httpError(res, "anthropic");
958
1092
  if (res.body === null)
959
1093
  throw new GatewayError("UPSTREAM", "empty response body", { provider: "anthropic" });
960
- return { events: decodeAnthropic(parseSse(res.body)), degradations };
1094
+ return { events: decodeAnthropic(parseSse(res.body)), degradations: notes };
961
1095
  }
962
1096
  };
963
- // packages/providers/src/anthropic/models.ts
964
- var ANTHROPIC_MODELS = {
965
- defaultModel: "claude-opus-5",
966
- models: [
967
- {
968
- id: "claude-fable-5",
969
- label: "Claude Fable 5",
970
- pricing: { input: 10, output: 50, cacheRead: 1, cacheWrite5m: 12.5, cacheWrite1h: 20 },
971
- limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
972
- },
973
- {
974
- id: "claude-opus-5",
975
- label: "Claude Opus 5",
976
- pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite5m: 6.25, cacheWrite1h: 10 },
977
- limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
978
- },
979
- {
980
- id: "claude-sonnet-5",
981
- label: "Claude Sonnet 5",
982
- pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 3.75, cacheWrite1h: 6 },
983
- limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
984
- },
985
- {
986
- id: "claude-haiku-4-5",
987
- label: "Claude Haiku 4.5",
988
- pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
989
- limits: { contextWindow: 200000, maxOutputTokens: 64000 }
990
- }
991
- ]
992
- };
993
-
994
- // packages/providers/src/kimi/models.ts
995
- var KIMI_MODELS = {
996
- defaultModel: "k3-256k",
997
- models: [
998
- {
999
- id: "k3-256k",
1000
- label: "Kimi K3 \u2014 256K",
1001
- pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
1002
- limits: { contextWindow: 262144, maxOutputTokens: 131072 }
1003
- },
1004
- {
1005
- id: "k3",
1006
- label: "Kimi K3 \u2014 up to 1M",
1007
- pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
1008
- limits: { contextWindow: 1048576, maxOutputTokens: 131072 }
1009
- },
1010
- {
1011
- id: "kimi-for-coding",
1012
- label: "Kimi K2.7 Code",
1013
- pricing: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
1014
- limits: { contextWindow: 262144, maxOutputTokens: 131072 }
1015
- },
1016
- {
1017
- id: "kimi-for-coding-highspeed",
1018
- label: "Kimi K2.7 Code \u2014 High Speed",
1019
- pricing: { input: 0.95, output: 8, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
1020
- limits: { contextWindow: 262144, maxOutputTokens: 131072 }
1021
- }
1022
- ]
1023
- };
1024
-
1025
- // packages/providers/src/openai/models.ts
1026
- var OPENAI_MODELS = {
1027
- defaultModel: "gpt-5.6",
1028
- models: [
1029
- {
1030
- id: "gpt-5.6",
1031
- label: "GPT-5.6 \u2014 routes to Sol",
1032
- pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
1033
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
1034
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
1035
- },
1036
- {
1037
- id: "gpt-5.6-sol",
1038
- label: "GPT-5.6 Sol \u2014 deepest reasoning",
1039
- pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
1040
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
1041
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
1042
- },
1043
- {
1044
- id: "gpt-5.6-terra",
1045
- label: "GPT-5.6 Terra \u2014 balanced",
1046
- pricing: { input: 2, output: 12, cacheRead: 0.2, cacheWrite5m: 0, cacheWrite1h: 0 },
1047
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
1048
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
1049
- },
1050
- {
1051
- id: "gpt-5.6-luna",
1052
- label: "GPT-5.6 Luna \u2014 fastest",
1053
- pricing: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite5m: 0, cacheWrite1h: 0 },
1054
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
1055
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
1056
- }
1057
- ]
1058
- };
1059
-
1060
- // packages/providers/src/catalog.ts
1061
- var PROVIDER_MODEL_CATALOG = {
1062
- anthropic: ANTHROPIC_MODELS,
1063
- openai: OPENAI_MODELS,
1064
- kimi: KIMI_MODELS
1065
- };
1066
- function catalogPricing(provider, model) {
1067
- return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
1068
- }
1069
- function catalogLimits(provider, model, auth = "apiKey") {
1070
- const entry = PROVIDER_MODEL_CATALOG[provider]?.models.find((choice) => choice.id === model);
1071
- if (entry === undefined)
1072
- return null;
1073
- return (auth === "oauth" ? entry.oauthLimits : undefined) ?? entry.limits;
1074
- }
1075
1097
  // packages/providers/src/http-client.ts
1076
1098
  import { request as httpRequest } from "http";
1077
1099
  import { request as httpsRequest } from "https";
@@ -1297,6 +1319,8 @@ function toChatWire(req, model) {
1297
1319
  if (!degradations.includes(d))
1298
1320
  degradations.push(d);
1299
1321
  };
1322
+ if (req.betas?.includes(CONTEXT_1M_BETA))
1323
+ note2("kimi:context-1m-dropped");
1300
1324
  const messages = [];
1301
1325
  const system = req.system?.flatMap((b) => b.type === "text" ? [b.text] : []).join(`
1302
1326
 
@@ -1573,6 +1597,8 @@ function toResponsesWire(req, model, opts = { oauth: false }) {
1573
1597
  if (!degradations.includes(d))
1574
1598
  degradations.push(d);
1575
1599
  };
1600
+ if (req.betas?.includes(CONTEXT_1M_BETA))
1601
+ note2("openai:context-1m-dropped");
1576
1602
  for (const message of req.messages) {
1577
1603
  const parts = [];
1578
1604
  const inlined = message.role === "system";
@@ -2261,12 +2287,10 @@ async function readSource(deps, source, lines) {
2261
2287
  return "";
2262
2288
  if (source.kind === "file")
2263
2289
  return deps.readFile(source.path, lines) ?? "";
2264
- const scope = source.scope === "system" ? [] : ["--user"];
2290
+ const unit = source.scope === "system" ? ["-u", source.unit] : [`--user-unit=${source.unit}`];
2265
2291
  const result = await deps.run([
2266
2292
  "journalctl",
2267
- ...scope,
2268
- "-u",
2269
- source.unit,
2293
+ ...unit,
2270
2294
  "-n",
2271
2295
  String(lines),
2272
2296
  "--no-pager",
@@ -16573,7 +16597,9 @@ var dryRunSchema = exports_external.object({
16573
16597
  reasoning: exports_external.boolean().default(false)
16574
16598
  }).strict();
16575
16599
  var modelSchema = exports_external.object({
16576
- id: exports_external.string().min(1),
16600
+ id: exports_external.string().min(1).refine((value) => !value.toLowerCase().startsWith("claude/"), {
16601
+ message: 'model id must not start with "claude/": that prefix is reserved for discovery mirrors'
16602
+ }),
16577
16603
  strategy: exports_external.enum(["score", "priority", "roundRobin", "weighted"]),
16578
16604
  isAlias: exports_external.boolean(),
16579
16605
  targets: exports_external.array(exports_external.object({
@@ -17985,6 +18011,60 @@ async function createKey(store, input) {
17985
18011
  async function revokeKey(store, id) {
17986
18012
  await store.keys.revoke(id);
17987
18013
  }
18014
+ // packages/control/src/modelLimits.ts
18015
+ function narrower(a, b) {
18016
+ const context = [a.contextWindow, b.contextWindow].filter((n) => n !== undefined);
18017
+ const output = [a.maxOutputTokens, b.maxOutputTokens].filter((n) => n !== undefined);
18018
+ return {
18019
+ ...context.length === 0 ? {} : { contextWindow: Math.min(...context) },
18020
+ ...output.length === 0 ? {} : { maxOutputTokens: Math.min(...output) }
18021
+ };
18022
+ }
18023
+ function targetLimits(target, auths) {
18024
+ const ways = auths.size === 0 ? ["apiKey"] : [...auths];
18025
+ let listed = {};
18026
+ for (const auth of ways) {
18027
+ const entry = catalogLimits(target.provider, target.model, auth);
18028
+ if (entry === null)
18029
+ continue;
18030
+ listed = narrower(listed, {
18031
+ contextWindow: entry.contextWindow,
18032
+ maxOutputTokens: entry.maxOutputTokens
18033
+ });
18034
+ }
18035
+ const contextWindow = target.contextWindow ?? listed.contextWindow;
18036
+ const maxOutputTokens = target.maxOutputTokens ?? listed.maxOutputTokens;
18037
+ return {
18038
+ ...contextWindow === undefined ? {} : { contextWindow },
18039
+ ...maxOutputTokens === undefined ? {} : { maxOutputTokens }
18040
+ };
18041
+ }
18042
+ function servingAuths(credentials) {
18043
+ const byProvider = new Map;
18044
+ for (const credential of credentials) {
18045
+ if (!credential.enabled)
18046
+ continue;
18047
+ const ways = byProvider.get(credential.provider) ?? new Set;
18048
+ ways.add(credential.authType);
18049
+ byProvider.set(credential.provider, ways);
18050
+ }
18051
+ return byProvider;
18052
+ }
18053
+ function resolveModelLimits(model, credentials) {
18054
+ const auths = servingAuths(credentials);
18055
+ let limits = {};
18056
+ for (const target of model.targets) {
18057
+ limits = narrower(limits, targetLimits(target, auths.get(target.provider) ?? new Set));
18058
+ }
18059
+ return limits;
18060
+ }
18061
+ function modelDisplayName(model) {
18062
+ const only = model.targets.length === 1 ? model.targets[0] : undefined;
18063
+ if (only === undefined)
18064
+ return model.id;
18065
+ const labelled = PROVIDER_MODEL_CATALOG[only.provider]?.models.find((choice) => choice.id === only.model);
18066
+ return labelled?.label ?? model.id;
18067
+ }
17988
18068
  // packages/control/src/models.ts
17989
18069
  async function listModels(store) {
17990
18070
  return store.config.listModels();
@@ -18341,6 +18421,71 @@ async function putSettings(store, input) {
18341
18421
  await store.config.putSettings(settings);
18342
18422
  return settings;
18343
18423
  }
18424
+ // packages/control/src/setup.ts
18425
+ var KEY_PLACEHOLDER = "<your OmniGateway key>";
18426
+ async function describeModelsForSetup(store) {
18427
+ const models = await listModels(store);
18428
+ const credentials = (await listCredentials(store)).map((credential) => ({
18429
+ provider: credential.provider,
18430
+ authType: credential.authType,
18431
+ enabled: credential.enabled
18432
+ }));
18433
+ return models.map((model) => ({
18434
+ model,
18435
+ limits: resolveModelLimits(model, credentials),
18436
+ label: modelDisplayName(model)
18437
+ }));
18438
+ }
18439
+ function slug(id) {
18440
+ return encodeURIComponent(id).replace(/\./g, "%2E");
18441
+ }
18442
+ function claudeProfiles(described, input) {
18443
+ const apiKey = input.apiKey ?? KEY_PLACEHOLDER;
18444
+ return described.map(({ model, limits }) => {
18445
+ const useMirror = input.discoveryMirrors === true && !/^(?:claude|anthropic)/i.test(model.id);
18446
+ const modelId = useMirror ? `claude/${model.id}` : model.id;
18447
+ const env2 = {
18448
+ ANTHROPIC_BASE_URL: input.baseUrl,
18449
+ ANTHROPIC_AUTH_TOKEN: apiKey,
18450
+ ANTHROPIC_MODEL: modelId,
18451
+ CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1"
18452
+ };
18453
+ if (limits.contextWindow !== undefined && !/^claude-/i.test(modelId)) {
18454
+ env2.CLAUDE_CODE_MAX_CONTEXT_TOKENS = String(limits.contextWindow);
18455
+ }
18456
+ return {
18457
+ path: `${slug(model.id)}/settings.json`,
18458
+ contents: `${JSON.stringify({ env: env2 }, null, 2)}
18459
+ `
18460
+ };
18461
+ });
18462
+ }
18463
+ function opencodeConfig(described, input) {
18464
+ const models = {};
18465
+ for (const { model, limits, label: label2 } of described) {
18466
+ const limit = limits.contextWindow === undefined ? undefined : {
18467
+ context: limits.contextWindow,
18468
+ ...limits.maxOutputTokens === undefined ? {} : { output: limits.maxOutputTokens }
18469
+ };
18470
+ models[model.id] = { name: label2, ...limit === undefined ? {} : { limit } };
18471
+ }
18472
+ const contents = `${JSON.stringify({
18473
+ $schema: "https://opencode.ai/config.json",
18474
+ provider: {
18475
+ omnigateway: {
18476
+ npm: "@ai-sdk/openai-compatible",
18477
+ name: "OmniGateway",
18478
+ options: {
18479
+ baseURL: `${input.baseUrl.replace(/\/+$/, "").replace(/(?:\/v1)+$/i, "")}/v1`,
18480
+ apiKey: input.apiKey ?? KEY_PLACEHOLDER
18481
+ },
18482
+ models
18483
+ }
18484
+ }
18485
+ }, null, 2)}
18486
+ `;
18487
+ return { path: "opencode.json", contents };
18488
+ }
18344
18489
  // packages/control/src/tail.ts
18345
18490
  import { closeSync, fstatSync, openSync, readSync, statSync } from "fs";
18346
18491
  var CHUNK = 64 * 1024;
@@ -19256,8 +19401,8 @@ var modelsShow = {
19256
19401
  price(target.costPerMTok.cacheRead),
19257
19402
  price(target.costPerMTok.cacheWrite5m),
19258
19403
  price(target.costPerMTok.cacheWrite1h),
19259
- tokens(target.contextWindow),
19260
- tokens(target.maxOutputTokens),
19404
+ tokens2(target.contextWindow),
19405
+ tokens2(target.maxOutputTokens),
19261
19406
  capabilityList(target)
19262
19407
  ]))
19263
19408
  ].join(`
@@ -19267,7 +19412,7 @@ var modelsShow = {
19267
19412
  function price(value) {
19268
19413
  return value === undefined ? "\u2014" : value.toFixed(2);
19269
19414
  }
19270
- function tokens(value) {
19415
+ function tokens2(value) {
19271
19416
  return value === undefined ? "\u2014" : value.toLocaleString("en-US");
19272
19417
  }
19273
19418
  function capabilityList(target) {
@@ -19335,9 +19480,9 @@ var modelsCatalog = {
19335
19480
  price(listed?.cacheRead),
19336
19481
  price(listed?.cacheWrite5m),
19337
19482
  price(listed?.cacheWrite1h),
19338
- tokens(limits?.contextWindow),
19339
- oauth?.contextWindow === limits?.contextWindow ? "\u2014" : tokens(oauth?.contextWindow),
19340
- tokens(limits?.maxOutputTokens)
19483
+ tokens2(limits?.contextWindow),
19484
+ oauth?.contextWindow === limits?.contextWindow ? "\u2014" : tokens2(oauth?.contextWindow),
19485
+ tokens2(limits?.maxOutputTokens)
19341
19486
  ];
19342
19487
  })));
19343
19488
  }
@@ -19721,6 +19866,78 @@ var settingsSet = {
19721
19866
  }
19722
19867
  };
19723
19868
 
19869
+ // apps/cli/src/commands/setup.ts
19870
+ import { dirname as dirname2, join as join4 } from "path";
19871
+ var OPTIONS = {
19872
+ dir: { type: "string" },
19873
+ key: { type: "string" },
19874
+ "dry-run": { type: "boolean" }
19875
+ };
19876
+ async function described(ctx) {
19877
+ const models = await describeModelsForSetup(await ctx.store());
19878
+ if (models.length === 0) {
19879
+ throw new CliError("no virtual models configured; add one with `omni models put`");
19880
+ }
19881
+ return models;
19882
+ }
19883
+ function baseUrl(ctx) {
19884
+ if (ctx.configError !== null)
19885
+ throw new CliError(ctx.configError);
19886
+ return ctx.config().baseUrl;
19887
+ }
19888
+ function finish(ctx, writer, files, dryRun2, key, write) {
19889
+ if (!dryRun2) {
19890
+ for (const file2 of files)
19891
+ write(file2.path, file2.contents);
19892
+ }
19893
+ emit(ctx, writer, { files }, () => dryRun2 ? files.map((f) => `--- ${f.path}
19894
+ ${f.contents}`).join(`
19895
+ `) : files.map((f) => f.path).join(`
19896
+ `));
19897
+ if (key === undefined) {
19898
+ note(ctx, writer, "no --key given: the files carry a placeholder, so paste your key over it");
19899
+ }
19900
+ }
19901
+ function at(dir, file2) {
19902
+ return { path: join4(dir, file2.path), contents: file2.contents };
19903
+ }
19904
+ var setupClaude = {
19905
+ usage: "setup claude [--dir <path>] [--key <key>] [--dry-run]",
19906
+ summary: "Write a Claude Code profile per virtual model",
19907
+ options: OPTIONS,
19908
+ async run(args, { ctx, writer, setupFs }) {
19909
+ const models = await described(ctx);
19910
+ const dir = stringFlag(args.values, "dir") ?? join4(setupFs.homeDir, ".claude", "profiles");
19911
+ const key = stringFlag(args.values, "key");
19912
+ const dryRun2 = boolFlag(args.values, "dry-run");
19913
+ const files = claudeProfiles(models, {
19914
+ baseUrl: baseUrl(ctx),
19915
+ discoveryMirrors: ctx.config().exposeClaudeCodeAliases,
19916
+ ...key === undefined ? {} : { apiKey: key }
19917
+ }).map((file2) => at(dir, file2));
19918
+ finish(ctx, writer, files, dryRun2, key, setupFs.write);
19919
+ if (!dryRun2) {
19920
+ note(ctx, writer, `run one with: CLAUDE_CONFIG_DIR=${dirname2(files[0]?.path ?? dir)} claude`);
19921
+ }
19922
+ }
19923
+ };
19924
+ var setupOpencode = {
19925
+ usage: "setup opencode [--dir <path>] [--key <key>] [--dry-run]",
19926
+ summary: "Write an opencode.json provider entry for this gateway",
19927
+ options: OPTIONS,
19928
+ async run(args, { ctx, writer, setupFs }) {
19929
+ const models = await described(ctx);
19930
+ const dir = stringFlag(args.values, "dir") ?? setupFs.cwd;
19931
+ const key = stringFlag(args.values, "key");
19932
+ const dryRun2 = boolFlag(args.values, "dry-run");
19933
+ const file2 = opencodeConfig(models, {
19934
+ baseUrl: baseUrl(ctx),
19935
+ ...key === undefined ? {} : { apiKey: key }
19936
+ });
19937
+ finish(ctx, writer, [at(dir, file2)], dryRun2, key, setupFs.write);
19938
+ }
19939
+ };
19940
+
19724
19941
  // apps/cli/src/commands/status.ts
19725
19942
  var WINDOW_ORDER = {
19726
19943
  fiveHour: 0,
@@ -19955,6 +20172,8 @@ var COMMANDS = {
19955
20172
  "keys revoke": keysRevoke,
19956
20173
  "settings get": settingsGet,
19957
20174
  "settings set": settingsSet,
20175
+ "setup claude": setupClaude,
20176
+ "setup opencode": setupOpencode,
19958
20177
  "admin set-password": adminSetPassword,
19959
20178
  "db migrate": dbMigrate
19960
20179
  };
@@ -20155,6 +20374,14 @@ async function run(argv, writer, options = {}) {
20155
20374
  ctx,
20156
20375
  writer,
20157
20376
  prompt,
20377
+ setupFs: options.setupFs ?? {
20378
+ homeDir: homedir3(),
20379
+ cwd: process.cwd(),
20380
+ write: (path, contents) => {
20381
+ mkdirSync2(dirname3(path), { recursive: true });
20382
+ writeFileSync2(path, contents);
20383
+ }
20384
+ },
20158
20385
  service: () => options.service?.({ root: ctx.root.root, env: ctx.env }) ?? createServiceDeps({ root: ctx.root.root, env: ctx.env, scope, now: ctx.now }),
20159
20386
  foreground: options.foreground ?? runForeground,
20160
20387
  connect: (store) => options.connect?.(store) ?? createConnectFlows({