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/gateway.js CHANGED
@@ -5543,6 +5543,54 @@ function parseJson(raw) {
5543
5543
  return {};
5544
5544
  }
5545
5545
  }
5546
+ // packages/ir/src/tokens.ts
5547
+ var CHARS_PER_TOKEN = 4;
5548
+ var IMAGE_TOKENS = 1600;
5549
+ var BLOCK_OVERHEAD = 4;
5550
+ var MESSAGE_OVERHEAD = 4;
5551
+ function fromText(text) {
5552
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
5553
+ }
5554
+ function blockTokens(block) {
5555
+ switch (block.type) {
5556
+ case "text":
5557
+ return BLOCK_OVERHEAD + fromText(block.text);
5558
+ case "image":
5559
+ return BLOCK_OVERHEAD + IMAGE_TOKENS;
5560
+ case "thinking":
5561
+ return BLOCK_OVERHEAD + fromText(block.text);
5562
+ case "toolUse":
5563
+ return BLOCK_OVERHEAD + fromText(block.name) + fromText(safeJson(block.input));
5564
+ case "toolResult":
5565
+ return BLOCK_OVERHEAD + fromText(block.toolUseId) + fromText(block.content);
5566
+ }
5567
+ }
5568
+ function messageTokens(message) {
5569
+ let total = MESSAGE_OVERHEAD;
5570
+ for (const block of message.content)
5571
+ total += blockTokens(block);
5572
+ return total;
5573
+ }
5574
+ function toolTokens(tool) {
5575
+ return BLOCK_OVERHEAD + fromText(tool.name) + fromText(tool.description ?? "") + fromText(safeJson(tool.inputSchema));
5576
+ }
5577
+ function safeJson(value) {
5578
+ try {
5579
+ return JSON.stringify(value) ?? "";
5580
+ } catch {
5581
+ return "";
5582
+ }
5583
+ }
5584
+ function estimateInputTokens(request) {
5585
+ let total = 0;
5586
+ for (const block of request.system ?? [])
5587
+ total += blockTokens(block);
5588
+ for (const message of request.messages)
5589
+ total += messageTokens(message);
5590
+ for (const tool of request.tools ?? [])
5591
+ total += toolTokens(tool);
5592
+ return total;
5593
+ }
5546
5594
  // packages/ir/src/validate.ts
5547
5595
  function validateRequest(req) {
5548
5596
  const seenToolUseIds = new Set;
@@ -5573,6 +5621,7 @@ function validateRequest(req) {
5573
5621
  }
5574
5622
  // packages/control/src/config.ts
5575
5623
  var MIN_KEY_LENGTH = 16;
5624
+ var TRUTHY = new Set(["1", "true", "yes", "on"]);
5576
5625
  var DECIMAL_INTEGER = /^\d+$/;
5577
5626
  function optionalText(value, fallback) {
5578
5627
  return value?.trim() || fallback;
@@ -5592,6 +5641,7 @@ function loadConfig(env) {
5592
5641
  const baseUrl = optionalText(env.OMNI_BASE_URL, derivedBaseUrl).replace(/\/+$/, "") || derivedBaseUrl;
5593
5642
  const staticDir = env.OMNI_STATIC_DIR?.trim();
5594
5643
  const logFile = env.OMNI_LOG_FILE?.trim();
5644
+ const exposeClaudeCodeAliases = TRUTHY.has((env.OMNI_EXPOSE_CLAUDE_CODE_ALIASES ?? "").trim().toLowerCase());
5595
5645
  const rawLogLevel = env.OMNI_LOG_LEVEL?.trim();
5596
5646
  const logLevel = parseLogLevel(rawLogLevel);
5597
5647
  return {
@@ -5603,9 +5653,14 @@ function loadConfig(env) {
5603
5653
  encryptionKey,
5604
5654
  baseUrl,
5605
5655
  staticDir: staticDir === undefined || staticDir.length === 0 ? null : staticDir,
5656
+ exposeClaudeCodeAliases,
5606
5657
  logFile: logFile === undefined || logFile.length === 0 ? null : logFile
5607
5658
  };
5608
5659
  }
5660
+ // packages/providers/src/betas.ts
5661
+ var CONTEXT_1M_BETA = "context-1m-2025-08-07";
5662
+ var CONTEXT_1M_TOKENS = 1e6;
5663
+
5609
5664
  // packages/providers/src/body.ts
5610
5665
  var BODY_ORDER = {
5611
5666
  anthropic: [
@@ -5713,6 +5768,119 @@ function signAnthropicBody(json) {
5713
5768
  return json.replace(needle, `cch=${computeCch(json)};`);
5714
5769
  }
5715
5770
 
5771
+ // packages/providers/src/anthropic/models.ts
5772
+ var ANTHROPIC_MODELS = {
5773
+ defaultModel: "claude-opus-5",
5774
+ models: [
5775
+ {
5776
+ id: "claude-fable-5",
5777
+ label: "Claude Fable 5",
5778
+ pricing: { input: 10, output: 50, cacheRead: 1, cacheWrite5m: 12.5, cacheWrite1h: 20 },
5779
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
5780
+ },
5781
+ {
5782
+ id: "claude-opus-5",
5783
+ label: "Claude Opus 5",
5784
+ pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite5m: 6.25, cacheWrite1h: 10 },
5785
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
5786
+ },
5787
+ {
5788
+ id: "claude-sonnet-5",
5789
+ label: "Claude Sonnet 5",
5790
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 3.75, cacheWrite1h: 6 },
5791
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
5792
+ },
5793
+ {
5794
+ id: "claude-haiku-4-5",
5795
+ label: "Claude Haiku 4.5",
5796
+ pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
5797
+ limits: { contextWindow: 200000, maxOutputTokens: 64000 }
5798
+ }
5799
+ ]
5800
+ };
5801
+
5802
+ // packages/providers/src/kimi/models.ts
5803
+ var KIMI_MODELS = {
5804
+ defaultModel: "k3-256k",
5805
+ models: [
5806
+ {
5807
+ id: "k3-256k",
5808
+ label: "Kimi K3 \u2014 256K",
5809
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
5810
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
5811
+ },
5812
+ {
5813
+ id: "k3",
5814
+ label: "Kimi K3 \u2014 up to 1M",
5815
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
5816
+ limits: { contextWindow: 1048576, maxOutputTokens: 131072 }
5817
+ },
5818
+ {
5819
+ id: "kimi-for-coding",
5820
+ label: "Kimi K2.7 Code",
5821
+ pricing: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
5822
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
5823
+ },
5824
+ {
5825
+ id: "kimi-for-coding-highspeed",
5826
+ label: "Kimi K2.7 Code \u2014 High Speed",
5827
+ pricing: { input: 0.95, output: 8, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
5828
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
5829
+ }
5830
+ ]
5831
+ };
5832
+
5833
+ // packages/providers/src/openai/models.ts
5834
+ var OPENAI_MODELS = {
5835
+ defaultModel: "gpt-5.6",
5836
+ models: [
5837
+ {
5838
+ id: "gpt-5.6",
5839
+ label: "GPT-5.6 \u2014 routes to Sol",
5840
+ pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
5841
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
5842
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
5843
+ },
5844
+ {
5845
+ id: "gpt-5.6-sol",
5846
+ label: "GPT-5.6 Sol \u2014 deepest reasoning",
5847
+ pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
5848
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
5849
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
5850
+ },
5851
+ {
5852
+ id: "gpt-5.6-terra",
5853
+ label: "GPT-5.6 Terra \u2014 balanced",
5854
+ pricing: { input: 2, output: 12, cacheRead: 0.2, cacheWrite5m: 0, cacheWrite1h: 0 },
5855
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
5856
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
5857
+ },
5858
+ {
5859
+ id: "gpt-5.6-luna",
5860
+ label: "GPT-5.6 Luna \u2014 fastest",
5861
+ pricing: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite5m: 0, cacheWrite1h: 0 },
5862
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
5863
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
5864
+ }
5865
+ ]
5866
+ };
5867
+
5868
+ // packages/providers/src/catalog.ts
5869
+ var PROVIDER_MODEL_CATALOG = {
5870
+ anthropic: ANTHROPIC_MODELS,
5871
+ openai: OPENAI_MODELS,
5872
+ kimi: KIMI_MODELS
5873
+ };
5874
+ function catalogPricing(provider, model) {
5875
+ return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
5876
+ }
5877
+ function catalogLimits(provider, model, auth = "apiKey") {
5878
+ const entry = PROVIDER_MODEL_CATALOG[provider]?.models.find((choice) => choice.id === model);
5879
+ if (entry === undefined)
5880
+ return null;
5881
+ return (auth === "oauth" ? entry.oauthLimits : undefined) ?? entry.limits;
5882
+ }
5883
+
5716
5884
  // packages/providers/src/http.ts
5717
5885
  function codeForStatus(status) {
5718
5886
  if (status === 401 || status === 403)
@@ -6270,6 +6438,15 @@ var anthropicAdapter = {
6270
6438
  ["Accept", "text/event-stream"]
6271
6439
  ];
6272
6440
  const betas = new Set(req.request.betas ?? []);
6441
+ const notes = [...degradations];
6442
+ if (betas.has(CONTEXT_1M_BETA)) {
6443
+ const limits = catalogLimits("anthropic", req.model, oauth ? "oauth" : "apiKey");
6444
+ const window2 = limits?.contextWindow;
6445
+ if (window2 !== undefined && window2 < CONTEXT_1M_TOKENS) {
6446
+ betas.delete(CONTEXT_1M_BETA);
6447
+ notes.push("anthropic:context-1m-dropped");
6448
+ }
6449
+ }
6273
6450
  if (oauth) {
6274
6451
  protocol.push(["Authorization", `Bearer ${req.credentials.accessToken}`]);
6275
6452
  betas.add(OAUTH_BETA);
@@ -6297,121 +6474,9 @@ var anthropicAdapter = {
6297
6474
  throw await httpError(res, "anthropic");
6298
6475
  if (res.body === null)
6299
6476
  throw new GatewayError("UPSTREAM", "empty response body", { provider: "anthropic" });
6300
- return { events: decodeAnthropic(parseSse(res.body)), degradations };
6477
+ return { events: decodeAnthropic(parseSse(res.body)), degradations: notes };
6301
6478
  }
6302
6479
  };
6303
- // packages/providers/src/anthropic/models.ts
6304
- var ANTHROPIC_MODELS = {
6305
- defaultModel: "claude-opus-5",
6306
- models: [
6307
- {
6308
- id: "claude-fable-5",
6309
- label: "Claude Fable 5",
6310
- pricing: { input: 10, output: 50, cacheRead: 1, cacheWrite5m: 12.5, cacheWrite1h: 20 },
6311
- limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
6312
- },
6313
- {
6314
- id: "claude-opus-5",
6315
- label: "Claude Opus 5",
6316
- pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite5m: 6.25, cacheWrite1h: 10 },
6317
- limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
6318
- },
6319
- {
6320
- id: "claude-sonnet-5",
6321
- label: "Claude Sonnet 5",
6322
- pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 3.75, cacheWrite1h: 6 },
6323
- limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
6324
- },
6325
- {
6326
- id: "claude-haiku-4-5",
6327
- label: "Claude Haiku 4.5",
6328
- pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
6329
- limits: { contextWindow: 200000, maxOutputTokens: 64000 }
6330
- }
6331
- ]
6332
- };
6333
-
6334
- // packages/providers/src/kimi/models.ts
6335
- var KIMI_MODELS = {
6336
- defaultModel: "k3-256k",
6337
- models: [
6338
- {
6339
- id: "k3-256k",
6340
- label: "Kimi K3 \u2014 256K",
6341
- pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
6342
- limits: { contextWindow: 262144, maxOutputTokens: 131072 }
6343
- },
6344
- {
6345
- id: "k3",
6346
- label: "Kimi K3 \u2014 up to 1M",
6347
- pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
6348
- limits: { contextWindow: 1048576, maxOutputTokens: 131072 }
6349
- },
6350
- {
6351
- id: "kimi-for-coding",
6352
- label: "Kimi K2.7 Code",
6353
- pricing: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
6354
- limits: { contextWindow: 262144, maxOutputTokens: 131072 }
6355
- },
6356
- {
6357
- id: "kimi-for-coding-highspeed",
6358
- label: "Kimi K2.7 Code \u2014 High Speed",
6359
- pricing: { input: 0.95, output: 8, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
6360
- limits: { contextWindow: 262144, maxOutputTokens: 131072 }
6361
- }
6362
- ]
6363
- };
6364
-
6365
- // packages/providers/src/openai/models.ts
6366
- var OPENAI_MODELS = {
6367
- defaultModel: "gpt-5.6",
6368
- models: [
6369
- {
6370
- id: "gpt-5.6",
6371
- label: "GPT-5.6 \u2014 routes to Sol",
6372
- pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
6373
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
6374
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
6375
- },
6376
- {
6377
- id: "gpt-5.6-sol",
6378
- label: "GPT-5.6 Sol \u2014 deepest reasoning",
6379
- pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
6380
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
6381
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
6382
- },
6383
- {
6384
- id: "gpt-5.6-terra",
6385
- label: "GPT-5.6 Terra \u2014 balanced",
6386
- pricing: { input: 2, output: 12, cacheRead: 0.2, cacheWrite5m: 0, cacheWrite1h: 0 },
6387
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
6388
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
6389
- },
6390
- {
6391
- id: "gpt-5.6-luna",
6392
- label: "GPT-5.6 Luna \u2014 fastest",
6393
- pricing: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite5m: 0, cacheWrite1h: 0 },
6394
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
6395
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
6396
- }
6397
- ]
6398
- };
6399
-
6400
- // packages/providers/src/catalog.ts
6401
- var PROVIDER_MODEL_CATALOG = {
6402
- anthropic: ANTHROPIC_MODELS,
6403
- openai: OPENAI_MODELS,
6404
- kimi: KIMI_MODELS
6405
- };
6406
- function catalogPricing(provider, model) {
6407
- return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
6408
- }
6409
- function catalogLimits(provider, model, auth = "apiKey") {
6410
- const entry = PROVIDER_MODEL_CATALOG[provider]?.models.find((choice) => choice.id === model);
6411
- if (entry === undefined)
6412
- return null;
6413
- return (auth === "oauth" ? entry.oauthLimits : undefined) ?? entry.limits;
6414
- }
6415
6480
  // packages/providers/src/http-client.ts
6416
6481
  import { request as httpRequest } from "http";
6417
6482
  import { request as httpsRequest } from "https";
@@ -6637,6 +6702,8 @@ function toChatWire(req, model) {
6637
6702
  if (!degradations.includes(d))
6638
6703
  degradations.push(d);
6639
6704
  };
6705
+ if (req.betas?.includes(CONTEXT_1M_BETA))
6706
+ note("kimi:context-1m-dropped");
6640
6707
  const messages = [];
6641
6708
  const system = req.system?.flatMap((b) => b.type === "text" ? [b.text] : []).join(`
6642
6709
 
@@ -6913,6 +6980,8 @@ function toResponsesWire(req, model, opts = { oauth: false }) {
6913
6980
  if (!degradations.includes(d))
6914
6981
  degradations.push(d);
6915
6982
  };
6983
+ if (req.betas?.includes(CONTEXT_1M_BETA))
6984
+ note("openai:context-1m-dropped");
6916
6985
  for (const message of req.messages) {
6917
6986
  const parts = [];
6918
6987
  const inlined = message.role === "system";
@@ -7607,12 +7676,10 @@ async function readSource(deps, source, lines) {
7607
7676
  return "";
7608
7677
  if (source.kind === "file")
7609
7678
  return deps.readFile(source.path, lines) ?? "";
7610
- const scope = source.scope === "system" ? [] : ["--user"];
7679
+ const unit = source.scope === "system" ? ["-u", source.unit] : [`--user-unit=${source.unit}`];
7611
7680
  const result = await deps.run([
7612
7681
  "journalctl",
7613
- ...scope,
7614
- "-u",
7615
- source.unit,
7682
+ ...unit,
7616
7683
  "-n",
7617
7684
  String(lines),
7618
7685
  "--no-pager",
@@ -21919,7 +21986,9 @@ var dryRunSchema = exports_external.object({
21919
21986
  reasoning: exports_external.boolean().default(false)
21920
21987
  }).strict();
21921
21988
  var modelSchema = exports_external.object({
21922
- id: exports_external.string().min(1),
21989
+ id: exports_external.string().min(1).refine((value) => !value.toLowerCase().startsWith("claude/"), {
21990
+ message: 'model id must not start with "claude/": that prefix is reserved for discovery mirrors'
21991
+ }),
21923
21992
  strategy: exports_external.enum(["score", "priority", "roundRobin", "weighted"]),
21924
21993
  isAlias: exports_external.boolean(),
21925
21994
  targets: exports_external.array(exports_external.object({
@@ -23373,6 +23442,60 @@ async function createKey(store, input) {
23373
23442
  async function revokeKey(store, id) {
23374
23443
  await store.keys.revoke(id);
23375
23444
  }
23445
+ // packages/control/src/modelLimits.ts
23446
+ function narrower(a, b) {
23447
+ const context = [a.contextWindow, b.contextWindow].filter((n) => n !== undefined);
23448
+ const output = [a.maxOutputTokens, b.maxOutputTokens].filter((n) => n !== undefined);
23449
+ return {
23450
+ ...context.length === 0 ? {} : { contextWindow: Math.min(...context) },
23451
+ ...output.length === 0 ? {} : { maxOutputTokens: Math.min(...output) }
23452
+ };
23453
+ }
23454
+ function targetLimits(target, auths) {
23455
+ const ways = auths.size === 0 ? ["apiKey"] : [...auths];
23456
+ let listed = {};
23457
+ for (const auth of ways) {
23458
+ const entry = catalogLimits(target.provider, target.model, auth);
23459
+ if (entry === null)
23460
+ continue;
23461
+ listed = narrower(listed, {
23462
+ contextWindow: entry.contextWindow,
23463
+ maxOutputTokens: entry.maxOutputTokens
23464
+ });
23465
+ }
23466
+ const contextWindow = target.contextWindow ?? listed.contextWindow;
23467
+ const maxOutputTokens = target.maxOutputTokens ?? listed.maxOutputTokens;
23468
+ return {
23469
+ ...contextWindow === undefined ? {} : { contextWindow },
23470
+ ...maxOutputTokens === undefined ? {} : { maxOutputTokens }
23471
+ };
23472
+ }
23473
+ function servingAuths(credentials) {
23474
+ const byProvider = new Map;
23475
+ for (const credential of credentials) {
23476
+ if (!credential.enabled)
23477
+ continue;
23478
+ const ways = byProvider.get(credential.provider) ?? new Set;
23479
+ ways.add(credential.authType);
23480
+ byProvider.set(credential.provider, ways);
23481
+ }
23482
+ return byProvider;
23483
+ }
23484
+ function resolveModelLimits(model, credentials) {
23485
+ const auths = servingAuths(credentials);
23486
+ let limits = {};
23487
+ for (const target of model.targets) {
23488
+ limits = narrower(limits, targetLimits(target, auths.get(target.provider) ?? new Set));
23489
+ }
23490
+ return limits;
23491
+ }
23492
+ function modelDisplayName(model) {
23493
+ const only = model.targets.length === 1 ? model.targets[0] : undefined;
23494
+ if (only === undefined)
23495
+ return model.id;
23496
+ const labelled = PROVIDER_MODEL_CATALOG[only.provider]?.models.find((choice) => choice.id === only.model);
23497
+ return labelled?.label ?? model.id;
23498
+ }
23376
23499
  // packages/control/src/models.ts
23377
23500
  async function listModels(store) {
23378
23501
  return store.config.listModels();
@@ -23789,6 +23912,75 @@ async function putSettings(store, input) {
23789
23912
  await store.config.putSettings(settings);
23790
23913
  return settings;
23791
23914
  }
23915
+ // packages/control/src/setup.ts
23916
+ var KEY_PLACEHOLDER = "<your OmniGateway key>";
23917
+ async function describeModelsForSetup(store) {
23918
+ const models = await listModels(store);
23919
+ const credentials = (await listCredentials(store)).map((credential) => ({
23920
+ provider: credential.provider,
23921
+ authType: credential.authType,
23922
+ enabled: credential.enabled
23923
+ }));
23924
+ return models.map((model) => ({
23925
+ model,
23926
+ limits: resolveModelLimits(model, credentials),
23927
+ label: modelDisplayName(model)
23928
+ }));
23929
+ }
23930
+ function slug(id) {
23931
+ return encodeURIComponent(id).replace(/\./g, "%2E");
23932
+ }
23933
+ function claudeProfiles(described, input) {
23934
+ const apiKey = input.apiKey ?? KEY_PLACEHOLDER;
23935
+ return described.map(({ model, limits }) => {
23936
+ const useMirror = input.discoveryMirrors === true && !/^(?:claude|anthropic)/i.test(model.id);
23937
+ const modelId = useMirror ? `claude/${model.id}` : model.id;
23938
+ const env2 = {
23939
+ ANTHROPIC_BASE_URL: input.baseUrl,
23940
+ ANTHROPIC_AUTH_TOKEN: apiKey,
23941
+ ANTHROPIC_MODEL: modelId,
23942
+ CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1"
23943
+ };
23944
+ if (limits.contextWindow !== undefined && !/^claude-/i.test(modelId)) {
23945
+ env2.CLAUDE_CODE_MAX_CONTEXT_TOKENS = String(limits.contextWindow);
23946
+ }
23947
+ return {
23948
+ path: `${slug(model.id)}/settings.json`,
23949
+ contents: `${JSON.stringify({ env: env2 }, null, 2)}
23950
+ `
23951
+ };
23952
+ });
23953
+ }
23954
+ function opencodeConfig(described, input) {
23955
+ const models = {};
23956
+ for (const { model, limits, label: label2 } of described) {
23957
+ const limit = limits.contextWindow === undefined ? undefined : {
23958
+ context: limits.contextWindow,
23959
+ ...limits.maxOutputTokens === undefined ? {} : { output: limits.maxOutputTokens }
23960
+ };
23961
+ models[model.id] = { name: label2, ...limit === undefined ? {} : { limit } };
23962
+ }
23963
+ const contents = `${JSON.stringify({
23964
+ $schema: "https://opencode.ai/config.json",
23965
+ provider: {
23966
+ omnigateway: {
23967
+ npm: "@ai-sdk/openai-compatible",
23968
+ name: "OmniGateway",
23969
+ options: {
23970
+ baseURL: `${input.baseUrl.replace(/\/+$/, "").replace(/(?:\/v1)+$/i, "")}/v1`,
23971
+ apiKey: input.apiKey ?? KEY_PLACEHOLDER
23972
+ },
23973
+ models
23974
+ }
23975
+ }
23976
+ }, null, 2)}
23977
+ `;
23978
+ return { path: "opencode.json", contents };
23979
+ }
23980
+ async function setupFiles(store, client, input) {
23981
+ const described = await describeModelsForSetup(store);
23982
+ return client === "claude" ? claudeProfiles(described, input) : [opencodeConfig(described, input)];
23983
+ }
23792
23984
  // packages/control/src/tail.ts
23793
23985
  import { closeSync, fstatSync, openSync, readSync, statSync } from "fs";
23794
23986
  var CHUNK = 64 * 1024;
@@ -38503,6 +38695,16 @@ function adminRoutes(deps) {
38503
38695
  await requireAdmin(request2, deps.admin);
38504
38696
  await removeModel(deps.store, params.id);
38505
38697
  return { ok: true };
38698
+ }).get("/api/agent-setup", async ({ request: request2, query }) => {
38699
+ await requireAdmin(request2, deps.admin);
38700
+ const client = query.client === "opencode" ? "opencode" : "claude";
38701
+ return {
38702
+ client,
38703
+ files: await setupFiles(deps.store, client, {
38704
+ baseUrl: deps.baseUrl,
38705
+ discoveryMirrors: deps.discoveryMirrors === true
38706
+ })
38707
+ };
38506
38708
  }).get("/api/keys", async ({ request: request2 }) => {
38507
38709
  await requireAdmin(request2, deps.admin);
38508
38710
  return { keys: await listKeys(deps.store) };
@@ -39361,6 +39563,30 @@ function openaiErrorBody(code, message) {
39361
39563
  return { error: { message, type: e.type, code: e.code } };
39362
39564
  }
39363
39565
 
39566
+ // apps/gateway/src/ingress/model.ts
39567
+ var DISCOVERY_PREFIX = "claude/";
39568
+ var ONE_M_SUFFIX = "[1m]";
39569
+ function normalizeClientModel(raw, betas2 = []) {
39570
+ let model = raw.trim();
39571
+ let wantsOneM = false;
39572
+ if (model.toLowerCase().endsWith(ONE_M_SUFFIX)) {
39573
+ const stripped = model.slice(0, -ONE_M_SUFFIX.length).trim();
39574
+ if (stripped.length > 0) {
39575
+ model = stripped;
39576
+ wantsOneM = true;
39577
+ }
39578
+ }
39579
+ if (model.toLowerCase().startsWith(DISCOVERY_PREFIX)) {
39580
+ const stripped = model.slice(DISCOVERY_PREFIX.length);
39581
+ if (stripped.length > 0)
39582
+ model = stripped;
39583
+ }
39584
+ const merged = [...betas2];
39585
+ if (wantsOneM && !merged.includes(CONTEXT_1M_BETA))
39586
+ merged.push(CONTEXT_1M_BETA);
39587
+ return { model, betas: merged };
39588
+ }
39589
+
39364
39590
  // apps/gateway/src/ingress/anthropic.ts
39365
39591
  var textBlock = exports_external.object({
39366
39592
  type: exports_external.literal("text"),
@@ -39542,8 +39768,9 @@ function parseAnthropicRequest(body2, headers) {
39542
39768
  text: b.text,
39543
39769
  ...irCacheControl(b.cache_control)
39544
39770
  }));
39771
+ const named = normalizeClientModel(parsed.model, readBetas(headers));
39545
39772
  const request2 = {
39546
- model: parsed.model,
39773
+ model: named.model,
39547
39774
  messages,
39548
39775
  stream: parsed.stream ?? false
39549
39776
  };
@@ -39574,9 +39801,8 @@ function parseAnthropicRequest(body2, headers) {
39574
39801
  const extras = extraFields(body2, KNOWN);
39575
39802
  if (extras !== undefined)
39576
39803
  request2.vendor = { anthropic: extras };
39577
- const betas = readBetas(headers);
39578
- if (betas.length > 0)
39579
- request2.betas = betas;
39804
+ if (named.betas.length > 0)
39805
+ request2.betas = named.betas;
39580
39806
  return validateRequest(request2);
39581
39807
  }
39582
39808
 
@@ -39725,7 +39951,7 @@ function parseOpenAIRequest(body2) {
39725
39951
  throw new GatewayError("BAD_REQUEST", "messages: at least one non-system message is required");
39726
39952
  }
39727
39953
  const request2 = {
39728
- model: parsed.model,
39954
+ model: normalizeClientModel(parsed.model).model,
39729
39955
  messages,
39730
39956
  stream: parsed.stream ?? false
39731
39957
  };
@@ -39759,65 +39985,13 @@ function parseOpenAIRequest(body2) {
39759
39985
 
39760
39986
  // apps/gateway/src/routes/models.ts
39761
39987
  var CREATED_AT = new Date(0).toISOString();
39762
- function narrower(a, b) {
39763
- const context = [a.contextWindow, b.contextWindow].filter((n) => n !== undefined);
39764
- const output = [a.maxOutputTokens, b.maxOutputTokens].filter((n) => n !== undefined);
39765
- return {
39766
- ...context.length === 0 ? {} : { contextWindow: Math.min(...context) },
39767
- ...output.length === 0 ? {} : { maxOutputTokens: Math.min(...output) }
39768
- };
39769
- }
39770
- function targetLimits(target, auths) {
39771
- const ways = auths.size === 0 ? ["apiKey"] : [...auths];
39772
- let listed = {};
39773
- for (const auth of ways) {
39774
- const entry = catalogLimits(target.provider, target.model, auth);
39775
- if (entry === null)
39776
- continue;
39777
- listed = narrower(listed, {
39778
- contextWindow: entry.contextWindow,
39779
- maxOutputTokens: entry.maxOutputTokens
39780
- });
39781
- }
39782
- const contextWindow = target.contextWindow ?? listed.contextWindow;
39783
- const maxOutputTokens = target.maxOutputTokens ?? listed.maxOutputTokens;
39784
- return {
39785
- ...contextWindow === undefined ? {} : { contextWindow },
39786
- ...maxOutputTokens === undefined ? {} : { maxOutputTokens }
39787
- };
39788
- }
39789
- function limitsOf(model, authsByProvider) {
39790
- let limits = {};
39791
- for (const target of model.targets) {
39792
- limits = narrower(limits, targetLimits(target, authsByProvider.get(target.provider) ?? new Set));
39793
- }
39794
- return limits;
39795
- }
39796
- function displayName(model) {
39797
- const only = model.targets.length === 1 ? model.targets[0] : undefined;
39798
- if (only === undefined)
39799
- return model.id;
39800
- const labelled = PROVIDER_MODEL_CATALOG[only.provider]?.models.find((choice) => choice.id === only.model);
39801
- return labelled?.label ?? model.id;
39802
- }
39803
- function servingAuths(credentials) {
39804
- const byProvider = new Map;
39805
- for (const credential of credentials) {
39806
- if (!credential.enabled)
39807
- continue;
39808
- const ways = byProvider.get(credential.provider) ?? new Set;
39809
- ways.add(credential.authType);
39810
- byProvider.set(credential.provider, ways);
39811
- }
39812
- return byProvider;
39813
- }
39814
39988
  function describeModel(model, credentials) {
39815
- const limits = limitsOf(model, servingAuths(credentials));
39989
+ const limits = resolveModelLimits(model, credentials);
39816
39990
  return {
39817
39991
  id: model.id,
39818
39992
  object: "model",
39819
39993
  type: "model",
39820
- display_name: displayName(model),
39994
+ display_name: modelDisplayName(model),
39821
39995
  created: 0,
39822
39996
  created_at: CREATED_AT,
39823
39997
  owned_by: "omnigateway",
@@ -39825,8 +39999,29 @@ function describeModel(model, credentials) {
39825
39999
  ...limits.maxOutputTokens === undefined ? {} : { max_tokens: limits.maxOutputTokens }
39826
40000
  };
39827
40001
  }
39828
- function modelListBody(models, credentials) {
39829
- const data = models.map((model) => describeModel(model, credentials));
40002
+ var ALREADY_CLAUDE = /^(?:claude|anthropic)/i;
40003
+ var MIRROR_PREFIX = "claude/";
40004
+ function discoveryMirrors(data) {
40005
+ const taken = new Set(data.map((entry) => entry.id));
40006
+ const mirrors = [];
40007
+ for (const entry of data) {
40008
+ if (ALREADY_CLAUDE.test(entry.id))
40009
+ continue;
40010
+ const id = `${MIRROR_PREFIX}${entry.id}`;
40011
+ if (taken.has(id))
40012
+ continue;
40013
+ mirrors.push({
40014
+ ...entry,
40015
+ id,
40016
+ root: entry.id,
40017
+ display_name: `${entry.display_name} (OmniGateway)`
40018
+ });
40019
+ }
40020
+ return mirrors;
40021
+ }
40022
+ function modelListBody(models, credentials, options = {}) {
40023
+ const described = models.map((model) => describeModel(model, credentials));
40024
+ const data = options.discoveryMirrors === true ? [...described, ...discoveryMirrors(described)] : described;
39830
40025
  return {
39831
40026
  object: "list",
39832
40027
  data,
@@ -40028,7 +40223,9 @@ function proxyRoutes(deps) {
40028
40223
  const snapshot = await dispatchDeps.snapshots.get(deps.now());
40029
40224
  const models = [...snapshot.models.values()];
40030
40225
  const visibleModels = key.modelAllowlist === null ? models : models.filter((model) => key.modelAllowlist?.includes(model.id));
40031
- return Response.json(modelListBody(visibleModels, snapshot.credentials));
40226
+ return Response.json(modelListBody(visibleModels, snapshot.credentials, {
40227
+ discoveryMirrors: deps.discoveryMirrors === true
40228
+ }));
40032
40229
  } catch (error51) {
40033
40230
  const gatewayError = asGatewayError(error51);
40034
40231
  logger2.error("model listing failed", {
@@ -40038,6 +40235,25 @@ function proxyRoutes(deps) {
40038
40235
  });
40039
40236
  return errorResponse("anthropic", gatewayError.code, gatewayError.message);
40040
40237
  }
40238
+ }).post("/v1/messages/count_tokens", async ({ request: request2 }) => {
40239
+ try {
40240
+ const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
40241
+ rateLimiter.consume(key.id, key.rateLimitPerMin);
40242
+ const body2 = await request2.json();
40243
+ const chatRequest = parseAnthropicRequest(body2, request2.headers);
40244
+ if (key.modelAllowlist !== null && !key.modelAllowlist.includes(chatRequest.model)) {
40245
+ throw new GatewayError("AUTH", `model "${chatRequest.model}" is not allowed for this API key`);
40246
+ }
40247
+ return Response.json({ input_tokens: estimateInputTokens(chatRequest) });
40248
+ } catch (error51) {
40249
+ const gatewayError = asGatewayError(error51);
40250
+ logger2.warn("token count failed", {
40251
+ status: HTTP_STATUS[gatewayError.code],
40252
+ code: gatewayError.code,
40253
+ reason: gatewayError.message
40254
+ });
40255
+ return errorResponse("anthropic", gatewayError.code, gatewayError.message);
40256
+ }
40041
40257
  }).onError(({ error: error51 }) => {
40042
40258
  const gatewayError = asGatewayError(error51);
40043
40259
  logger2.error("unhandled proxy route error", {
@@ -40085,10 +40301,13 @@ function createApp(deps) {
40085
40301
  refresh,
40086
40302
  requestId,
40087
40303
  rateLimiter,
40088
- logger: logger2
40304
+ logger: logger2,
40305
+ discoveryMirrors: deps.discoveryMirrors === true
40089
40306
  })).use(adminRoutes({
40090
40307
  store: deps.store,
40091
40308
  admin,
40309
+ baseUrl: deps.baseUrl,
40310
+ discoveryMirrors: deps.discoveryMirrors === true,
40092
40311
  now,
40093
40312
  sessionTtlMs: ADMIN_SESSION_TTL_MS,
40094
40313
  logger: logger2,
@@ -40331,7 +40550,8 @@ async function main() {
40331
40550
  refresh,
40332
40551
  staticDir,
40333
40552
  logger: logger2,
40334
- console: console2
40553
+ console: console2,
40554
+ discoveryMirrors: config2.exposeClaudeCodeAliases
40335
40555
  });
40336
40556
  const stopMaintenance = startMaintenance({ store, now, logger: logger2 });
40337
40557
  const stopRefreshScheduler = startRefreshScheduler({ store, refresh, now, logger: logger2 });