omnigateway 0.1.5 → 0.1.7

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 (39) hide show
  1. package/README.md +55 -2
  2. package/bin/omni.js +709 -325
  3. package/gateway.js +1028 -264
  4. package/package.json +1 -1
  5. package/public/assets/{Chip-Ccqsb_Mq.js → Chip-C69u2U_b.js} +2 -2
  6. package/public/assets/{CopyValue-CvEMy9Zo.js → CopyValue-CJy2T0H6.js} +1 -1
  7. package/public/assets/{Field-Devm4K46.js → Field-Sc8mDqJv.js} +1 -1
  8. package/public/assets/{Lamp-BXSUIAtG.js → Lamp-7jD_9929.js} +2 -2
  9. package/public/assets/{Meter-Fn8X7t60.js → Meter-C0nUftAa.js} +2 -2
  10. package/public/assets/{Modal-C4y9zALa.js → Modal-CiVHSJmm.js} +2 -2
  11. package/public/assets/Rack-CMqGBP4I.js +147 -0
  12. package/public/assets/{Readout-BKpOZIGX.js → Readout-Dv2dZWtI.js} +4 -4
  13. package/public/assets/{States-DBzsiHjQ.js → States-D55MhqMx.js} +10 -10
  14. package/public/assets/{Table-DPDJeeVc.js → Table-DBB3jdgi.js} +1 -1
  15. package/public/assets/{Toggle-CBq5XAMU.js → Toggle-BoGjb5oi.js} +3 -3
  16. package/public/assets/_app-p5Mz7sXD.js +1 -0
  17. package/public/assets/_app.accounts-B5wMO9TH.js +51 -0
  18. package/public/assets/_app.console-Bka7AkiL.js +25 -0
  19. package/public/assets/_app.index-MnQuadXb.js +61 -0
  20. package/public/assets/_app.keys-BX8k8COP.js +39 -0
  21. package/public/assets/_app.logs-DD-sd4fn.js +33 -0
  22. package/public/assets/_app.models-CezFNL6y.js +144 -0
  23. package/public/assets/{_app.settings-Du5Nsp9f.js → _app.settings-D1taAt8B.js} +3 -3
  24. package/public/assets/{_app.usage-DKq4Hsia.js → _app.usage-BRhEvwVr.js} +28 -28
  25. package/public/assets/catalog-C_OQ0icG.js +1 -0
  26. package/public/assets/index-vCuK1fRQ.js +170 -0
  27. package/public/assets/{login-Gyf-1fSa.js → login-DaC-wVpI.js} +8 -8
  28. package/public/assets/{queries-BMwc6EvM.js → queries-2E6-CboE.js} +12 -12
  29. package/public/assets/{trash-2-DFVxurNH.js → trash-2--aH2iKg-.js} +1 -1
  30. package/public/index.html +2 -2
  31. package/public/assets/Rack-CRQSwSeB.js +0 -147
  32. package/public/assets/_app-VCodWes4.js +0 -1
  33. package/public/assets/_app.accounts-BP3L84us.js +0 -51
  34. package/public/assets/_app.index-C5Vd-zlp.js +0 -61
  35. package/public/assets/_app.keys-0e1BHrIZ.js +0 -39
  36. package/public/assets/_app.logs-DNSND96R.js +0 -33
  37. package/public/assets/_app.models-DzFIbnem.js +0 -144
  38. package/public/assets/catalog-DVRPlBJJ.js +0 -1
  39. package/public/assets/index-BYyeRTQJ.js +0 -170
package/gateway.js CHANGED
@@ -535,7 +535,7 @@ function flushChunk(parts, chunk) {
535
535
  }
536
536
  function pushCodeUnit(parts, chunk, codeUnit) {
537
537
  chunk.push(codeUnit);
538
- if (chunk.length >= CHUNK)
538
+ if (chunk.length >= CHUNK2)
539
539
  flushChunk(parts, chunk);
540
540
  }
541
541
  function pushCodePoint(parts, chunk, cp) {
@@ -660,8 +660,8 @@ function decodeUTF16LE(bytes) {
660
660
  }
661
661
  function decodeASCII(bytes) {
662
662
  const parts = [];
663
- for (let i = 0;i < bytes.length; i += CHUNK) {
664
- const end = Math.min(bytes.length, i + CHUNK);
663
+ for (let i = 0;i < bytes.length; i += CHUNK2) {
664
+ const end = Math.min(bytes.length, i + CHUNK2);
665
665
  const codes = new Array(end - i);
666
666
  for (let j = i, k2 = 0;j < end; j++, k2++) {
667
667
  codes[k2] = bytes[j] & 127;
@@ -672,8 +672,8 @@ function decodeASCII(bytes) {
672
672
  }
673
673
  function decodeLatin1(bytes) {
674
674
  const parts = [];
675
- for (let i = 0;i < bytes.length; i += CHUNK) {
676
- const end = Math.min(bytes.length, i + CHUNK);
675
+ for (let i = 0;i < bytes.length; i += CHUNK2) {
676
+ const end = Math.min(bytes.length, i + CHUNK2);
677
677
  const codes = new Array(end - i);
678
678
  for (let j = i, k2 = 0;j < end; j++, k2++) {
679
679
  codes[k2] = bytes[j];
@@ -689,7 +689,7 @@ function decodeWindows1252(bytes) {
689
689
  const b = bytes[i];
690
690
  const extra = b >= 128 && b <= 159 ? WINDOWS_1252_EXTRA[b] : undefined;
691
691
  out += extra !== null && extra !== undefined ? extra : String.fromCharCode(b);
692
- if (out.length >= CHUNK) {
692
+ if (out.length >= CHUNK2) {
693
693
  parts.push(out);
694
694
  out = "";
695
695
  }
@@ -698,7 +698,7 @@ function decodeWindows1252(bytes) {
698
698
  parts.push(out);
699
699
  return parts.join("");
700
700
  }
701
- var WINDOWS_1252_EXTRA, WINDOWS_1252_REVERSE, _utf8Decoder, CHUNK, REPLACEMENT = 65533;
701
+ var WINDOWS_1252_EXTRA, WINDOWS_1252_REVERSE, _utf8Decoder, CHUNK2, REPLACEMENT = 65533;
702
702
  var init_lib = __esm(() => {
703
703
  WINDOWS_1252_EXTRA = {
704
704
  128: "\u20AC",
@@ -733,7 +733,7 @@ var init_lib = __esm(() => {
733
733
  for (const [code, char] of Object.entries(WINDOWS_1252_EXTRA)) {
734
734
  WINDOWS_1252_REVERSE[char] = Number.parseInt(code, 10);
735
735
  }
736
- CHUNK = 32 * 1024;
736
+ CHUNK2 = 32 * 1024;
737
737
  });
738
738
 
739
739
  // node_modules/.bun/token-types@6.1.2/node_modules/token-types/lib/index.js
@@ -5295,35 +5295,6 @@ function createAdminAuth(store, opts) {
5295
5295
  }
5296
5296
  };
5297
5297
  }
5298
- // packages/control/src/config.ts
5299
- var MIN_KEY_LENGTH = 16;
5300
- var DECIMAL_INTEGER = /^\d+$/;
5301
- function optionalText(value, fallback) {
5302
- return value?.trim() || fallback;
5303
- }
5304
- function loadConfig(env) {
5305
- const encryptionKey = env.OMNI_ENCRYPTION_KEY;
5306
- if (typeof encryptionKey !== "string" || encryptionKey.length < MIN_KEY_LENGTH) {
5307
- throw new Error(`OMNI_ENCRYPTION_KEY must be set to at least ${MIN_KEY_LENGTH} characters. ` + "Generate one with: openssl rand -base64 32");
5308
- }
5309
- const host = optionalText(env.OMNI_HOST, "127.0.0.1");
5310
- const rawPort = env.OMNI_PORT ?? "9000";
5311
- const port = Number(rawPort);
5312
- if (!DECIMAL_INTEGER.test(rawPort) || !Number.isInteger(port) || port < 1 || port > 65535) {
5313
- throw new Error(`OMNI_PORT must be an integer between 1 and 65535, got "${rawPort}"`);
5314
- }
5315
- const derivedBaseUrl = `http://${host}:${port}`;
5316
- const baseUrl = optionalText(env.OMNI_BASE_URL, derivedBaseUrl).replace(/\/+$/, "") || derivedBaseUrl;
5317
- const staticDir = env.OMNI_STATIC_DIR?.trim();
5318
- return {
5319
- port,
5320
- host,
5321
- databasePath: optionalText(env.OMNI_DB_PATH, "./omnigateway.db"),
5322
- encryptionKey,
5323
- baseUrl,
5324
- staticDir: staticDir === undefined || staticDir.length === 0 ? null : staticDir
5325
- };
5326
- }
5327
5298
  // packages/ir/src/capabilities.ts
5328
5299
  var PROVIDER_CAPABILITIES = {
5329
5300
  anthropic: { tools: true, images: true, reasoning: true },
@@ -5380,7 +5351,134 @@ class GatewayError extends Error {
5380
5351
  this.retryAfterMs = opts?.retryAfterMs;
5381
5352
  }
5382
5353
  }
5354
+ // packages/ir/src/logger.ts
5355
+ var LEVELS = { debug: 10, info: 20, warn: 30, error: 40 };
5356
+ var FIELD_ORDER = [
5357
+ "requestId",
5358
+ "surface",
5359
+ "status",
5360
+ "provider",
5361
+ "model",
5362
+ "requestedModel",
5363
+ "credentialId",
5364
+ "apiKeyId",
5365
+ "attempt",
5366
+ "attempts",
5367
+ "code",
5368
+ "retryable",
5369
+ "stream",
5370
+ "inputTokens",
5371
+ "outputTokens",
5372
+ "cacheReadTokens",
5373
+ "cacheWriteTokens",
5374
+ "costUsd",
5375
+ "ttftMs",
5376
+ "durationMs",
5377
+ "retryAfterMs",
5378
+ "count",
5379
+ "rawCount",
5380
+ "dailyCount",
5381
+ "host",
5382
+ "port",
5383
+ "path",
5384
+ "reason"
5385
+ ];
5386
+ var REASON_MAX = 200;
5387
+ var LEVEL_COLOR = {
5388
+ debug: "\x1B[2;37m",
5389
+ info: "\x1B[36m",
5390
+ warn: "\x1B[33m",
5391
+ error: "\x1B[31m"
5392
+ };
5393
+ var RESET = "\x1B[0m";
5394
+ var NEEDS_QUOTING = /[\s="]/;
5395
+ function renderValue(value) {
5396
+ if (value === null)
5397
+ return "null";
5398
+ if (typeof value !== "string")
5399
+ return String(value);
5400
+ return NEEDS_QUOTING.test(value) ? JSON.stringify(value) : value;
5401
+ }
5402
+ function formatLine(level, at, msg, fields, color) {
5403
+ const label = level.toUpperCase().padEnd(5, " ");
5404
+ const head = `${new Date(at).toISOString()} ${color ? `${LEVEL_COLOR[level]}${label}${RESET}` : label} ${msg}`;
5405
+ if (fields === undefined)
5406
+ return head;
5407
+ const parts = [];
5408
+ for (const key of FIELD_ORDER) {
5409
+ const value = fields[key];
5410
+ if (value === undefined)
5411
+ continue;
5412
+ const capped = key === "reason" && typeof value === "string" && value.length > REASON_MAX ? `${value.slice(0, REASON_MAX)}\u2026` : value;
5413
+ parts.push(`${key}=${renderValue(capped)}`);
5414
+ }
5415
+ return parts.length === 0 ? head : `${head} ${parts.join(" ")}`;
5416
+ }
5417
+ var ANSI = /\u001B\[[0-9;]*m/g;
5418
+ var HEAD = /^(\S+) (DEBUG|INFO|WARN|ERROR) {1,2}(.*)$/;
5419
+ var FIELDS = / {2}(?=[a-zA-Z]+=)/;
5420
+ function parseLine(raw) {
5421
+ const absent = { raw, at: null, level: null, msg: null };
5422
+ const match = HEAD.exec(raw.replace(ANSI, ""));
5423
+ if (match === null)
5424
+ return absent;
5425
+ const [, instant, label, tail] = match;
5426
+ if (instant === undefined || label === undefined || tail === undefined)
5427
+ return absent;
5428
+ const at = Date.parse(instant);
5429
+ if (Number.isNaN(at))
5430
+ return absent;
5431
+ const msg = tail.split(FIELDS, 1)[0] ?? tail;
5432
+ return { raw, at, level: label.toLowerCase(), msg };
5433
+ }
5434
+ function createLogger(opts) {
5435
+ const threshold = LEVELS[opts.level];
5436
+ const now = opts.now ?? (() => Date.now());
5437
+ const color = opts.color ?? false;
5438
+ const emit = (level, msg, fields) => {
5439
+ if (LEVELS[level] < threshold)
5440
+ return;
5441
+ try {
5442
+ opts.write(formatLine(level, now(), msg, fields, color));
5443
+ } catch {}
5444
+ };
5445
+ return {
5446
+ debug: (msg, fields) => emit("debug", msg, fields),
5447
+ info: (msg, fields) => emit("info", msg, fields),
5448
+ warn: (msg, fields) => emit("warn", msg, fields),
5449
+ error: (msg, fields) => emit("error", msg, fields),
5450
+ enabled: (level) => LEVELS[level] >= threshold
5451
+ };
5452
+ }
5453
+ var noopLogger = {
5454
+ debug: () => {},
5455
+ info: () => {},
5456
+ warn: () => {},
5457
+ error: () => {},
5458
+ enabled: () => false
5459
+ };
5460
+ function parseLogLevel(value) {
5461
+ const normalized = value?.trim().toLowerCase();
5462
+ if (normalized === undefined || normalized.length === 0)
5463
+ return null;
5464
+ return Object.hasOwn(LEVELS, normalized) ? normalized : null;
5465
+ }
5466
+ // packages/ir/src/request.ts
5467
+ function cacheControlOf(block) {
5468
+ return block.type === "thinking" ? undefined : block.cacheControl;
5469
+ }
5383
5470
  // packages/ir/src/stream.ts
5471
+ function usageFromPromptTotal(promptTokens, outputTokens, cacheReadTokens, cacheWriteTokens = 0) {
5472
+ return {
5473
+ inputTokens: Math.max(0, promptTokens - cacheReadTokens - cacheWriteTokens),
5474
+ outputTokens,
5475
+ cacheReadTokens,
5476
+ cacheWriteTokens
5477
+ };
5478
+ }
5479
+ function promptTokens(usage) {
5480
+ return usage.inputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
5481
+ }
5384
5482
  function collect(events) {
5385
5483
  let id = "";
5386
5484
  let model = "";
@@ -5410,7 +5508,7 @@ function collect(events) {
5410
5508
  else if (ev.delta.type === "thinking" && acc.kind === "thinking")
5411
5509
  acc.text += ev.delta.text;
5412
5510
  else if (ev.delta.type === "thinkingSignature" && acc.kind === "thinking")
5413
- acc.signature = ev.delta.signature;
5511
+ acc.signature = (acc.signature ?? "") + ev.delta.signature;
5414
5512
  else if (ev.delta.type === "toolJson" && acc.kind === "toolUse")
5415
5513
  acc.json += ev.delta.partial;
5416
5514
  break;
@@ -5473,6 +5571,41 @@ function validateRequest(req) {
5473
5571
  }
5474
5572
  return { ...req, messages: cleaned };
5475
5573
  }
5574
+ // packages/control/src/config.ts
5575
+ var MIN_KEY_LENGTH = 16;
5576
+ var DECIMAL_INTEGER = /^\d+$/;
5577
+ function optionalText(value, fallback) {
5578
+ return value?.trim() || fallback;
5579
+ }
5580
+ function loadConfig(env) {
5581
+ const encryptionKey = env.OMNI_ENCRYPTION_KEY;
5582
+ if (typeof encryptionKey !== "string" || encryptionKey.length < MIN_KEY_LENGTH) {
5583
+ throw new Error(`OMNI_ENCRYPTION_KEY must be set to at least ${MIN_KEY_LENGTH} characters. ` + "Generate one with: openssl rand -base64 32");
5584
+ }
5585
+ const host = optionalText(env.OMNI_HOST, "127.0.0.1");
5586
+ const rawPort = env.OMNI_PORT ?? "9000";
5587
+ const port = Number(rawPort);
5588
+ if (!DECIMAL_INTEGER.test(rawPort) || !Number.isInteger(port) || port < 1 || port > 65535) {
5589
+ throw new Error(`OMNI_PORT must be an integer between 1 and 65535, got "${rawPort}"`);
5590
+ }
5591
+ const derivedBaseUrl = `http://${host}:${port}`;
5592
+ const baseUrl = optionalText(env.OMNI_BASE_URL, derivedBaseUrl).replace(/\/+$/, "") || derivedBaseUrl;
5593
+ const staticDir = env.OMNI_STATIC_DIR?.trim();
5594
+ const logFile = env.OMNI_LOG_FILE?.trim();
5595
+ const rawLogLevel = env.OMNI_LOG_LEVEL?.trim();
5596
+ const logLevel = parseLogLevel(rawLogLevel);
5597
+ return {
5598
+ logLevel: logLevel ?? "info",
5599
+ logLevelFallbackFrom: logLevel === null && rawLogLevel ? rawLogLevel : null,
5600
+ port,
5601
+ host,
5602
+ databasePath: optionalText(env.OMNI_DB_PATH, "./omnigateway.db"),
5603
+ encryptionKey,
5604
+ baseUrl,
5605
+ staticDir: staticDir === undefined || staticDir.length === 0 ? null : staticDir,
5606
+ logFile: logFile === undefined || logFile.length === 0 ? null : logFile
5607
+ };
5608
+ }
5476
5609
  // packages/providers/src/body.ts
5477
5610
  var BODY_ORDER = {
5478
5611
  anthropic: [
@@ -5559,8 +5692,13 @@ function applyAnthropicSystem(system) {
5559
5692
  let rewritten = text;
5560
5693
  for (const [from, to] of REWRITES)
5561
5694
  rewritten = rewritten.replaceAll(from, to);
5562
- if (rewritten.trim().length > 0)
5563
- kept.push({ type: "text", text: rewritten });
5695
+ if (rewritten.trim().length > 0) {
5696
+ kept.push({
5697
+ type: "text",
5698
+ text: rewritten,
5699
+ ...block.cache_control === undefined ? {} : { cache_control: block.cache_control }
5700
+ });
5701
+ }
5564
5702
  }
5565
5703
  return [{ type: "text", text: billingBlock() }, { type: "text", text: AGENT_PREAMBLE }, ...kept];
5566
5704
  }
@@ -5864,6 +6002,8 @@ async function* decodeAnthropic(messages) {
5864
6002
  let inputTokens = 0;
5865
6003
  let cacheReadTokens = 0;
5866
6004
  let cacheWriteTokens = 0;
6005
+ let cacheWrite5mTokens;
6006
+ let cacheWrite1hTokens;
5867
6007
  let outputTokens = 0;
5868
6008
  let stopReason = "endTurn";
5869
6009
  let terminal = false;
@@ -5877,6 +6017,8 @@ async function* decodeAnthropic(messages) {
5877
6017
  inputTokens = m.usage?.input_tokens ?? 0;
5878
6018
  cacheReadTokens = m.usage?.cache_read_input_tokens ?? 0;
5879
6019
  cacheWriteTokens = m.usage?.cache_creation_input_tokens ?? 0;
6020
+ cacheWrite5mTokens = m.usage?.cache_creation?.ephemeral_5m_input_tokens;
6021
+ cacheWrite1hTokens = m.usage?.cache_creation?.ephemeral_1h_input_tokens;
5880
6022
  yield { type: "start", id: String(m.id ?? ""), model: String(m.model ?? "") };
5881
6023
  break;
5882
6024
  }
@@ -5886,7 +6028,7 @@ async function* decodeAnthropic(messages) {
5886
6028
  if (cb.type === "text")
5887
6029
  yield { type: "blockStart", index, block: { type: "text" } };
5888
6030
  else if (cb.type === "thinking")
5889
- yield { type: "blockStart", index, block: { type: "thinking" } };
6031
+ yield { type: "blockStart", index, block: { type: "thinking", signed: true } };
5890
6032
  else if (cb.type === "tool_use")
5891
6033
  yield {
5892
6034
  type: "blockStart",
@@ -5906,7 +6048,7 @@ async function* decodeAnthropic(messages) {
5906
6048
  index,
5907
6049
  delta: { type: "thinking", text: delta.thinking ?? "" }
5908
6050
  };
5909
- else if (delta.type === "signature_delta")
6051
+ else if (delta.type === "signature_delta" && (delta.signature ?? "") !== "")
5910
6052
  yield {
5911
6053
  type: "blockDelta",
5912
6054
  index,
@@ -5936,7 +6078,14 @@ async function* decodeAnthropic(messages) {
5936
6078
  yield {
5937
6079
  type: "end",
5938
6080
  stopReason,
5939
- usage: { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens }
6081
+ usage: {
6082
+ inputTokens,
6083
+ outputTokens,
6084
+ cacheReadTokens,
6085
+ cacheWriteTokens,
6086
+ ...cacheWrite5mTokens === undefined ? {} : { cacheWrite5mTokens },
6087
+ ...cacheWrite1hTokens === undefined ? {} : { cacheWrite1hTokens }
6088
+ }
5940
6089
  };
5941
6090
  break;
5942
6091
  case "error": {
@@ -5966,25 +6115,36 @@ async function* decodeAnthropic(messages) {
5966
6115
 
5967
6116
  // packages/providers/src/anthropic/wire.ts
5968
6117
  var OAUTH_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude.";
6118
+ function wireCacheControl(c) {
6119
+ if (c === undefined)
6120
+ return {};
6121
+ return { cache_control: { type: c.type, ...c.ttl === undefined ? {} : { ttl: c.ttl } } };
6122
+ }
6123
+ function isReplayableThinking(b) {
6124
+ return b.type !== "thinking" || b.signature !== undefined && b.signature !== "";
6125
+ }
5969
6126
  function encodeBlock(b) {
6127
+ const cache = wireCacheControl(cacheControlOf(b));
5970
6128
  switch (b.type) {
5971
6129
  case "text":
5972
- return { type: "text", text: b.text };
6130
+ return { type: "text", text: b.text, ...cache };
5973
6131
  case "image":
5974
6132
  return {
5975
6133
  type: "image",
5976
- source: { type: "base64", media_type: b.mediaType, data: b.data }
6134
+ source: { type: "base64", media_type: b.mediaType, data: b.data },
6135
+ ...cache
5977
6136
  };
5978
6137
  case "thinking":
5979
6138
  return { type: "thinking", thinking: b.text, signature: b.signature };
5980
6139
  case "toolUse":
5981
- return { type: "tool_use", id: b.id, name: b.name, input: b.input };
6140
+ return { type: "tool_use", id: b.id, name: b.name, input: b.input, ...cache };
5982
6141
  case "toolResult":
5983
6142
  return {
5984
6143
  type: "tool_result",
5985
6144
  tool_use_id: b.toolUseId,
5986
6145
  content: b.content,
5987
- is_error: b.isError
6146
+ is_error: b.isError,
6147
+ ...cache
5988
6148
  };
5989
6149
  }
5990
6150
  }
@@ -5992,6 +6152,16 @@ function encodeSystemTurn(content) {
5992
6152
  return content.flatMap((b) => b.type === "text" ? [b.text] : []).join(`
5993
6153
  `);
5994
6154
  }
6155
+ function systemCacheControl(req) {
6156
+ const cacheable = req.messages.flatMap((message) => message.content.flatMap((block) => block.type === "thinking" ? [] : [{ role: message.role, block }]));
6157
+ const markedSystemBlocks = cacheable.filter(({ role, block }) => role === "system" && cacheControlOf(block) !== undefined);
6158
+ const final = markedSystemBlocks.at(-1);
6159
+ const promoted = final !== undefined && final.block.type === "text" && cacheable.at(-1) === final ? cacheControlOf(final.block) : undefined;
6160
+ return {
6161
+ ...promoted === undefined ? {} : { promoted },
6162
+ lost: markedSystemBlocks.length > (promoted === undefined ? 0 : 1)
6163
+ };
6164
+ }
5995
6165
  function encodeToolChoice(c) {
5996
6166
  switch (c.type) {
5997
6167
  case "auto":
@@ -6006,16 +6176,39 @@ function encodeToolChoice(c) {
6006
6176
  }
6007
6177
  function toWire(req, model, opts) {
6008
6178
  const degradations = [];
6009
- let system = req.system?.flatMap((b) => b.type === "text" ? [{ type: "text", text: b.text }] : []);
6179
+ const note = (d) => {
6180
+ if (!degradations.includes(d))
6181
+ degradations.push(d);
6182
+ };
6183
+ let system = req.system?.flatMap((b) => b.type === "text" ? [{ type: "text", text: b.text, ...wireCacheControl(b.cacheControl) }] : []);
6010
6184
  if (opts.oauth && system?.[0]?.text !== OAUTH_IDENTITY) {
6011
6185
  system = [{ type: "text", text: OAUTH_IDENTITY }, ...system ?? []];
6012
- degradations.push("anthropic:oauth-system-prefix");
6186
+ note("anthropic:oauth-system-prefix");
6013
6187
  }
6188
+ const systemCache = systemCacheControl(req);
6189
+ if (systemCache.lost)
6190
+ note("anthropic:system-turn-cache-control-dropped");
6014
6191
  const body = {
6015
6192
  model,
6016
- messages: req.messages.map((m) => m.role === "system" ? { role: m.role, content: encodeSystemTurn(m.content) } : { role: m.role, content: m.content.map(encodeBlock) }),
6193
+ messages: req.messages.flatMap((m) => {
6194
+ if (m.role !== "system") {
6195
+ const replayable = m.content.filter(isReplayableThinking);
6196
+ if (replayable.length !== m.content.length)
6197
+ note("anthropic:unsigned-thinking-dropped");
6198
+ if (replayable.length === 0)
6199
+ return [];
6200
+ return [{ role: m.role, content: replayable.map(encodeBlock) }];
6201
+ }
6202
+ return [{ role: m.role, content: encodeSystemTurn(m.content) }];
6203
+ }),
6017
6204
  max_tokens: req.maxTokens ?? 4096,
6018
- stream: req.stream
6205
+ stream: req.stream,
6206
+ ...systemCache.promoted === undefined ? {} : {
6207
+ cache_control: {
6208
+ type: systemCache.promoted.type,
6209
+ ...systemCache.promoted.ttl === undefined ? {} : { ttl: systemCache.promoted.ttl }
6210
+ }
6211
+ }
6019
6212
  };
6020
6213
  if (system !== undefined && system.length > 0)
6021
6214
  body.system = system;
@@ -6027,7 +6220,8 @@ function toWire(req, model, opts) {
6027
6220
  body.tools = req.tools.map((t) => ({
6028
6221
  name: t.name,
6029
6222
  ...t.description === undefined ? {} : { description: t.description },
6030
- input_schema: t.inputSchema
6223
+ input_schema: t.inputSchema,
6224
+ ...wireCacheControl(t.cacheControl)
6031
6225
  }));
6032
6226
  }
6033
6227
  if (req.toolChoice !== undefined)
@@ -6067,12 +6261,13 @@ var anthropicAdapter = {
6067
6261
  const { body, degradations } = toWire(req.request, req.model, { oauth });
6068
6262
  const withSystem = {
6069
6263
  ...body,
6070
- system: applyAnthropicSystem(body.system ?? [])
6264
+ system: applyAnthropicSystem(body.system ?? []),
6265
+ stream: true
6071
6266
  };
6072
6267
  const protocol = [
6073
6268
  ["Content-Type", "application/json"],
6074
6269
  ["anthropic-version", API_VERSION],
6075
- ["Accept", req.request.stream ? "text/event-stream" : "application/json"]
6270
+ ["Accept", "text/event-stream"]
6076
6271
  ];
6077
6272
  const betas = new Set(req.request.betas ?? []);
6078
6273
  if (oauth) {
@@ -6091,6 +6286,7 @@ var anthropicAdapter = {
6091
6286
  const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
6092
6287
  const bodyString = signAnthropicBody(JSON.stringify(orderFields(withSystem, BODY_ORDER.anthropic)));
6093
6288
  const res = await req.http({
6289
+ provider: "anthropic",
6094
6290
  url: BASE_URL,
6095
6291
  method: "POST",
6096
6292
  headers,
@@ -6111,22 +6307,26 @@ var ANTHROPIC_MODELS = {
6111
6307
  {
6112
6308
  id: "claude-fable-5",
6113
6309
  label: "Claude Fable 5",
6114
- pricing: { input: 10, output: 50, cacheRead: 1 }
6310
+ pricing: { input: 10, output: 50, cacheRead: 1, cacheWrite5m: 12.5, cacheWrite1h: 20 },
6311
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
6115
6312
  },
6116
6313
  {
6117
6314
  id: "claude-opus-5",
6118
6315
  label: "Claude Opus 5",
6119
- pricing: { input: 5, output: 25, cacheRead: 0.5 }
6316
+ pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite5m: 6.25, cacheWrite1h: 10 },
6317
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
6120
6318
  },
6121
6319
  {
6122
6320
  id: "claude-sonnet-5",
6123
6321
  label: "Claude Sonnet 5",
6124
- pricing: { input: 3, output: 15, cacheRead: 0.3 }
6322
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 3.75, cacheWrite1h: 6 },
6323
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
6125
6324
  },
6126
6325
  {
6127
6326
  id: "claude-haiku-4-5",
6128
6327
  label: "Claude Haiku 4.5",
6129
- pricing: { input: 1, output: 5, cacheRead: 0.1 }
6328
+ pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
6329
+ limits: { contextWindow: 200000, maxOutputTokens: 64000 }
6130
6330
  }
6131
6331
  ]
6132
6332
  };
@@ -6138,22 +6338,26 @@ var KIMI_MODELS = {
6138
6338
  {
6139
6339
  id: "k3-256k",
6140
6340
  label: "Kimi K3 \u2014 256K",
6141
- pricing: { input: 3, output: 15, cacheRead: 0.3 }
6341
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
6342
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
6142
6343
  },
6143
6344
  {
6144
6345
  id: "k3",
6145
6346
  label: "Kimi K3 \u2014 up to 1M",
6146
- pricing: { input: 3, output: 15, cacheRead: 0.3 }
6347
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
6348
+ limits: { contextWindow: 1048576, maxOutputTokens: 131072 }
6147
6349
  },
6148
6350
  {
6149
6351
  id: "kimi-for-coding",
6150
6352
  label: "Kimi K2.7 Code",
6151
- pricing: { input: 0.95, output: 4, cacheRead: 0.19 }
6353
+ pricing: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
6354
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
6152
6355
  },
6153
6356
  {
6154
6357
  id: "kimi-for-coding-highspeed",
6155
6358
  label: "Kimi K2.7 Code \u2014 High Speed",
6156
- pricing: { input: 0.95, output: 8, cacheRead: 0.19 }
6359
+ pricing: { input: 0.95, output: 8, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
6360
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
6157
6361
  }
6158
6362
  ]
6159
6363
  };
@@ -6165,22 +6369,30 @@ var OPENAI_MODELS = {
6165
6369
  {
6166
6370
  id: "gpt-5.6",
6167
6371
  label: "GPT-5.6 \u2014 routes to Sol",
6168
- pricing: { input: 5, output: 30, cacheRead: 0.5 }
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 }
6169
6375
  },
6170
6376
  {
6171
6377
  id: "gpt-5.6-sol",
6172
6378
  label: "GPT-5.6 Sol \u2014 deepest reasoning",
6173
- pricing: { input: 5, output: 30, cacheRead: 0.5 }
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 }
6174
6382
  },
6175
6383
  {
6176
6384
  id: "gpt-5.6-terra",
6177
6385
  label: "GPT-5.6 Terra \u2014 balanced",
6178
- pricing: { input: 2, output: 12, cacheRead: 0.2 }
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 }
6179
6389
  },
6180
6390
  {
6181
6391
  id: "gpt-5.6-luna",
6182
6392
  label: "GPT-5.6 Luna \u2014 fastest",
6183
- pricing: { input: 0.2, output: 1.2, cacheRead: 0.02 }
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 }
6184
6396
  }
6185
6397
  ]
6186
6398
  };
@@ -6192,15 +6404,38 @@ var PROVIDER_MODEL_CATALOG = {
6192
6404
  kimi: KIMI_MODELS
6193
6405
  };
6194
6406
  function catalogPricing(provider, model) {
6195
- return PROVIDER_MODEL_CATALOG[provider].models.find((entry) => entry.id === model)?.pricing ?? null;
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;
6196
6414
  }
6197
6415
  // packages/providers/src/http-client.ts
6198
6416
  import { request as httpRequest } from "http";
6199
6417
  import { request as httpsRequest } from "https";
6200
6418
  import { Readable } from "stream";
6201
- function nodeHttpClient() {
6419
+ function nodeHttpClient(options = {}) {
6420
+ const logger2 = options.logger ?? noopLogger;
6421
+ const now = options.now ?? (() => Date.now());
6202
6422
  return (req) => new Promise((resolve, reject) => {
6203
6423
  const url = new URL(req.url);
6424
+ const startedAt = now();
6425
+ let traced = false;
6426
+ const trace = (status, failed = false) => {
6427
+ if (traced || !logger2.enabled("debug"))
6428
+ return;
6429
+ traced = true;
6430
+ logger2.debug("upstream http", {
6431
+ provider: req.provider,
6432
+ status,
6433
+ host: url.host,
6434
+ path: url.pathname,
6435
+ durationMs: now() - startedAt,
6436
+ reason: failed ? "transport error" : undefined
6437
+ });
6438
+ };
6204
6439
  const send = url.protocol === "https:" ? httpsRequest : httpRequest;
6205
6440
  const bodyBytes = Buffer.from(req.body, "utf8");
6206
6441
  const headers = {};
@@ -6228,6 +6463,7 @@ function nodeHttpClient() {
6228
6463
  else if (typeof v === "string")
6229
6464
  responseHeaders.set(k, v);
6230
6465
  }
6466
+ trace(incoming.statusCode);
6231
6467
  resolve({
6232
6468
  status: incoming.statusCode ?? 0,
6233
6469
  headers: responseHeaders,
@@ -6243,17 +6479,17 @@ function nodeHttpClient() {
6243
6479
  });
6244
6480
  });
6245
6481
  const onAbort = () => outgoing.destroy(new Error("aborted"));
6246
- if (req.signal.aborted) {
6247
- outgoing.destroy(new Error("aborted"));
6248
- reject(new Error("aborted"));
6249
- return;
6250
- }
6251
- req.signal.addEventListener("abort", onAbort, { once: true });
6252
6482
  outgoing.on("error", (err) => {
6253
6483
  req.signal.removeEventListener("abort", onAbort);
6484
+ trace(undefined, true);
6254
6485
  reject(err);
6255
6486
  });
6256
6487
  outgoing.on("close", () => req.signal.removeEventListener("abort", onAbort));
6488
+ if (req.signal.aborted) {
6489
+ outgoing.destroy(new Error("aborted"));
6490
+ return;
6491
+ }
6492
+ req.signal.addEventListener("abort", onAbort, { once: true });
6257
6493
  if (bodyBytes.byteLength > 0)
6258
6494
  outgoing.write(bodyBytes);
6259
6495
  outgoing.end();
@@ -6322,12 +6558,8 @@ async function* decodeChat(messages) {
6322
6558
  yield { type: "start", id: String(d.id ?? ""), model: String(d.model ?? "") };
6323
6559
  }
6324
6560
  if (d.usage) {
6325
- usage = {
6326
- inputTokens: d.usage.prompt_tokens ?? 0,
6327
- outputTokens: d.usage.completion_tokens ?? 0,
6328
- cacheReadTokens: d.usage.prompt_cache_hit_tokens ?? 0,
6329
- cacheWriteTokens: 0
6330
- };
6561
+ const details = d.usage.prompt_tokens_details;
6562
+ usage = usageFromPromptTotal(d.usage.prompt_tokens ?? 0, d.usage.completion_tokens ?? 0, details?.cached_tokens ?? d.usage.prompt_cache_hit_tokens ?? 0, details?.cache_creation_tokens ?? details?.cache_write_tokens ?? 0);
6331
6563
  }
6332
6564
  const choice = d.choices?.[0];
6333
6565
  if (!choice)
@@ -6453,7 +6685,12 @@ function toChatWire(req, model) {
6453
6685
  `) });
6454
6686
  }
6455
6687
  }
6456
- const body = { model, messages, stream: req.stream };
6688
+ const body = {
6689
+ model,
6690
+ messages,
6691
+ stream: req.stream,
6692
+ stream_options: { include_usage: true }
6693
+ };
6457
6694
  if (req.maxTokens !== undefined)
6458
6695
  body.max_tokens = req.maxTokens;
6459
6696
  if (req.temperature !== undefined)
@@ -6494,6 +6731,7 @@ var kimiAdapter = {
6494
6731
  const profile = PROFILES.kimi;
6495
6732
  const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
6496
6733
  const res = await req.http({
6734
+ provider: "kimi",
6497
6735
  url: BASE_URL2,
6498
6736
  method: "POST",
6499
6737
  headers,
@@ -6624,12 +6862,7 @@ async function* decodeResponses(messages) {
6624
6862
  yield {
6625
6863
  type: "end",
6626
6864
  stopReason,
6627
- usage: {
6628
- inputTokens: r.usage?.input_tokens ?? 0,
6629
- outputTokens: r.usage?.output_tokens ?? 0,
6630
- cacheReadTokens: r.usage?.input_tokens_details?.cached_tokens ?? 0,
6631
- cacheWriteTokens: 0
6632
- }
6865
+ usage: usageFromPromptTotal(r.usage?.input_tokens ?? 0, r.usage?.output_tokens ?? 0, r.usage?.input_tokens_details?.cached_tokens ?? r.usage?.prompt_tokens_details?.cached_tokens ?? 0)
6633
6866
  };
6634
6867
  break;
6635
6868
  }
@@ -6802,6 +7035,7 @@ var openaiAdapter = {
6802
7035
  const profile = PROFILES.openai;
6803
7036
  const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
6804
7037
  const res = await req.http({
7038
+ provider: "openai",
6805
7039
  url: oauth ? OAUTH_URL : API_URL,
6806
7040
  method: "POST",
6807
7041
  headers,
@@ -6822,13 +7056,14 @@ var ADAPTERS = {
6822
7056
  kimi: kimiAdapter
6823
7057
  };
6824
7058
  // packages/control/src/oauth/types.ts
6825
- async function postJson(deps, url, profile2, opts) {
7059
+ async function postJson(deps, provider, url, profile2, opts) {
6826
7060
  const headers = orderHeaders(mergeHeaders(profile2.headers, [
6827
7061
  ["Content-Type", opts.contentType],
6828
7062
  ["Accept", "application/json"],
6829
7063
  ...opts.extraHeaders ?? []
6830
7064
  ]), profile2.order);
6831
7065
  const res = await deps.http({
7066
+ provider,
6832
7067
  url,
6833
7068
  method: "POST",
6834
7069
  headers,
@@ -6842,13 +7077,14 @@ async function postJson(deps, url, profile2, opts) {
6842
7077
  } catch {}
6843
7078
  return { status: res.status, parsed };
6844
7079
  }
6845
- async function getJson(deps, url, profile2, opts) {
7080
+ async function getJson(deps, provider, url, profile2, opts) {
6846
7081
  const headers = orderHeaders(mergeHeaders(profile2.headers, [
6847
7082
  ["Authorization", `Bearer ${opts.accessToken}`],
6848
7083
  ["Accept", "application/json"],
6849
7084
  ...opts.extraHeaders ?? []
6850
7085
  ]), profile2.order);
6851
7086
  const res = await deps.http({
7087
+ provider,
6852
7088
  url,
6853
7089
  method: "GET",
6854
7090
  headers,
@@ -7042,7 +7278,7 @@ function deviceCodeFrom(value) {
7042
7278
  };
7043
7279
  }
7044
7280
  async function post(url, body2, device, deps) {
7045
- const { status, parsed } = await postJson(deps, url, PROFILES.kimi, {
7281
+ const { status, parsed } = await postJson(deps, "kimi", url, PROFILES.kimi, {
7046
7282
  contentType: "application/x-www-form-urlencoded",
7047
7283
  body: new URLSearchParams({ ...body2, client_id: CLIENT_ID }).toString(),
7048
7284
  extraHeaders: kimiDeviceHeaders(device)
@@ -7145,7 +7381,7 @@ var kimiOAuth = {
7145
7381
  if (secrets.accessToken === null)
7146
7382
  return null;
7147
7383
  const device = deviceFrom(providerData);
7148
- const { status, parsed } = await getJson(deps, USAGE_URL, PROFILES.kimi, {
7384
+ const { status, parsed } = await getJson(deps, "kimi", USAGE_URL, PROFILES.kimi, {
7149
7385
  accessToken: secrets.accessToken,
7150
7386
  extraHeaders: kimiDeviceHeaders(device)
7151
7387
  });
@@ -7214,6 +7450,7 @@ function deviceIdFrom(start) {
7214
7450
  return deviceId;
7215
7451
  }
7216
7452
  function createConnectFlows(deps) {
7453
+ const logger2 = deps.logger ?? noopLogger;
7217
7454
  const flows = createPendingFlows({ now: deps.now, ttlMs: FLOW_TTL_MS });
7218
7455
  const pollsInFlight = new Map;
7219
7456
  const callbackUri = (provider) => provider === "openai" ? "http://localhost:1455/auth/callback" : "";
@@ -7258,6 +7495,10 @@ function createConnectFlows(deps) {
7258
7495
  disabledAt: null,
7259
7496
  ...result.secrets
7260
7497
  });
7498
+ logger2.info("oauth connect completed", {
7499
+ provider: flow.provider,
7500
+ credentialId: id
7501
+ });
7261
7502
  return { id };
7262
7503
  }
7263
7504
  async function pollOutcome(flowId, poll) {
@@ -7330,6 +7571,62 @@ function createConnectFlows(deps) {
7330
7571
  }
7331
7572
  };
7332
7573
  }
7574
+ // packages/control/src/console.ts
7575
+ var UNIT_NAME = "omnigateway.service";
7576
+ var MAX_CONSOLE_LINES = 500;
7577
+ var DEFAULT_CONSOLE_LINES = 200;
7578
+ function resolveConsoleSource(input) {
7579
+ const path = input.logFile?.trim();
7580
+ if (path !== undefined && path.length > 0)
7581
+ return { kind: "file", path };
7582
+ if (input.unitInstalled)
7583
+ return { kind: "journal", unit: UNIT_NAME, scope: input.scope };
7584
+ return { kind: "none" };
7585
+ }
7586
+ function consoleLimit(requested) {
7587
+ const value = requested === undefined ? DEFAULT_CONSOLE_LINES : Number(requested);
7588
+ if (!Number.isFinite(value))
7589
+ return DEFAULT_CONSOLE_LINES;
7590
+ return Math.floor(Math.min(Math.max(1, value), MAX_CONSOLE_LINES));
7591
+ }
7592
+ function keep(line, query) {
7593
+ if (query.since !== undefined && (line.at === null || line.at <= query.since))
7594
+ return false;
7595
+ if (query.level === undefined || line.level === null)
7596
+ return true;
7597
+ return LEVELS[line.level] >= LEVELS[query.level];
7598
+ }
7599
+ var SCAN_FACTOR = 20;
7600
+ var SCAN_LIMIT = 5000;
7601
+ function scanWidth(query) {
7602
+ const filtered = query.level !== undefined || query.since !== undefined;
7603
+ return filtered ? Math.min(query.lines * SCAN_FACTOR, SCAN_LIMIT) : query.lines;
7604
+ }
7605
+ async function readSource(deps, source, lines) {
7606
+ if (source.kind === "none")
7607
+ return "";
7608
+ if (source.kind === "file")
7609
+ return deps.readFile(source.path, lines) ?? "";
7610
+ const scope = source.scope === "system" ? [] : ["--user"];
7611
+ const result = await deps.run([
7612
+ "journalctl",
7613
+ ...scope,
7614
+ "-u",
7615
+ source.unit,
7616
+ "-n",
7617
+ String(lines),
7618
+ "--no-pager",
7619
+ "--output=cat"
7620
+ ]);
7621
+ return result.code === 0 ? result.stdout : "";
7622
+ }
7623
+ async function readConsole(deps, source, query) {
7624
+ const limited = { ...query, lines: consoleLimit(query.lines) };
7625
+ const text = await readSource(deps, source, scanWidth(limited));
7626
+ const lines = text.split(`
7627
+ `).filter((raw) => raw.trim().length > 0).map(parseLine).filter((line) => keep(line, limited)).slice(-limited.lines);
7628
+ return source.kind === "file" ? { source: "file", path: source.path, lines } : { source: source.kind, lines };
7629
+ }
7333
7630
  // node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
7334
7631
  var exports_external = {};
7335
7632
  __export(exports_external, {
@@ -21633,28 +21930,18 @@ var modelSchema = exports_external.object({
21633
21930
  costPerMTok: exports_external.object({
21634
21931
  input: exports_external.number().min(0),
21635
21932
  output: exports_external.number().min(0),
21636
- cacheRead: exports_external.number().min(0)
21637
- }),
21638
- capabilities: exports_external.object({
21639
- tools: exports_external.boolean(),
21640
- images: exports_external.boolean(),
21641
- reasoning: exports_external.boolean()
21642
- })
21643
- }).or(exports_external.object({
21644
- provider: providerIdSchema,
21645
- model: exports_external.string().min(1),
21646
- tier: exports_external.number().int().min(1),
21647
- weight: exports_external.number().positive(),
21648
- costPerMTok: exports_external.object({
21649
- input: exports_external.number().min(0),
21650
- output: exports_external.number().min(0)
21933
+ cacheRead: exports_external.number().min(0).optional(),
21934
+ cacheWrite5m: exports_external.number().min(0).optional(),
21935
+ cacheWrite1h: exports_external.number().min(0).optional()
21651
21936
  }),
21937
+ contextWindow: exports_external.number().int().positive().optional(),
21938
+ maxOutputTokens: exports_external.number().int().positive().optional(),
21652
21939
  capabilities: exports_external.object({
21653
21940
  tools: exports_external.boolean(),
21654
21941
  images: exports_external.boolean(),
21655
21942
  reasoning: exports_external.boolean()
21656
21943
  })
21657
- }))).min(1, "a virtual model needs at least one target")
21944
+ })).min(1, "a virtual model needs at least one target")
21658
21945
  });
21659
21946
  var keyCreateSchema = exports_external.object({
21660
21947
  label: exports_external.string().min(1).default("api key"),
@@ -21748,6 +22035,12 @@ async function patchCredential(deps, id, input) {
21748
22035
  ...patch.tier === undefined ? {} : { tier: patch.tier },
21749
22036
  ...patch.weight === undefined ? {} : { weight: patch.weight }
21750
22037
  });
22038
+ if (patch.enabled !== undefined && patch.enabled !== existing.enabled) {
22039
+ (deps.logger ?? noopLogger).info(patch.enabled ? "credential enabled" : "credential disabled", {
22040
+ credentialId: id,
22041
+ provider: existing.provider
22042
+ });
22043
+ }
21751
22044
  }
21752
22045
  async function removeCredential(store, id) {
21753
22046
  await store.credentials.remove(id);
@@ -22009,12 +22302,20 @@ var PREFIX_PROVIDER = [
22009
22302
  ];
22010
22303
  function synthesize(provider, model) {
22011
22304
  const listed = catalogPricing(provider, model);
22305
+ const limits = catalogLimits(provider, model);
22012
22306
  const target = {
22013
22307
  provider,
22014
22308
  model,
22015
22309
  tier: 1,
22016
22310
  weight: 1,
22017
- costPerMTok: listed === null ? { input: 0, output: 0 } : { input: listed.input, output: listed.output, cacheRead: listed.cacheRead },
22311
+ costPerMTok: listed === null ? { input: 0, output: 0 } : {
22312
+ input: listed.input,
22313
+ output: listed.output,
22314
+ cacheRead: listed.cacheRead,
22315
+ cacheWrite5m: listed.cacheWrite5m,
22316
+ cacheWrite1h: listed.cacheWrite1h
22317
+ },
22318
+ ...limits === null ? {} : { contextWindow: limits.contextWindow, maxOutputTokens: limits.maxOutputTokens },
22018
22319
  capabilities: PROVIDER_CAPABILITIES[provider]
22019
22320
  };
22020
22321
  return { id: `${provider}/${model}`, targets: [target], strategy: "score", isAlias: true };
@@ -23017,7 +23318,9 @@ function createUsageRepo(db) {
23017
23318
 
23018
23319
  // packages/store/src/sqlite/store.ts
23019
23320
  async function createStore(opts) {
23321
+ const logger2 = opts.logger ?? noopLogger;
23020
23322
  const db = openDb(opts.path);
23323
+ logger2.debug("store opened", { path: opts.path });
23021
23324
  const listeners = new Set;
23022
23325
  const emit = (change) => {
23023
23326
  for (const listener of listeners) {
@@ -23104,7 +23407,7 @@ var REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
23104
23407
  var SCOPES = "org:create_api_key user:profile user:inference";
23105
23408
  var USAGE_URL2 = "https://api.anthropic.com/api/oauth/usage";
23106
23409
  async function postToken(body2, deps) {
23107
- const { status, parsed } = await postJson(deps, TOKEN_URL2, PROFILES.anthropic, {
23410
+ const { status, parsed } = await postJson(deps, "anthropic", TOKEN_URL2, PROFILES.anthropic, {
23108
23411
  contentType: "application/json",
23109
23412
  body: JSON.stringify({ ...body2, client_id: CLIENT_ID2 })
23110
23413
  });
@@ -23182,7 +23485,7 @@ var anthropicOAuth = {
23182
23485
  async usage(secrets, deps) {
23183
23486
  if (secrets.accessToken === null)
23184
23487
  return null;
23185
- const { status, parsed } = await getJson(deps, USAGE_URL2, PROFILES.anthropic, {
23488
+ const { status, parsed } = await getJson(deps, "anthropic", USAGE_URL2, PROFILES.anthropic, {
23186
23489
  accessToken: secrets.accessToken,
23187
23490
  extraHeaders: [
23188
23491
  ["anthropic-beta", "oauth-2025-04-20"],
@@ -23240,7 +23543,7 @@ function parseTokenResponse(value) {
23240
23543
  };
23241
23544
  }
23242
23545
  async function postToken2(body2, deps) {
23243
- const { status, parsed } = await postJson(deps, TOKEN_URL3, PROFILES.openai, {
23546
+ const { status, parsed } = await postJson(deps, "openai", TOKEN_URL3, PROFILES.openai, {
23244
23547
  contentType: "application/x-www-form-urlencoded",
23245
23548
  body: new URLSearchParams({ ...body2, client_id: CLIENT_ID3 }).toString()
23246
23549
  });
@@ -23339,7 +23642,7 @@ var openaiOAuth = {
23339
23642
  if (secrets.accessToken === null)
23340
23643
  return null;
23341
23644
  const accountId = accountIdFromProviderData(providerData);
23342
- const { status, parsed } = await getJson(deps, USAGE_URL3, PROFILES.openai, {
23645
+ const { status, parsed } = await getJson(deps, "openai", USAGE_URL3, PROFILES.openai, {
23343
23646
  accessToken: secrets.accessToken,
23344
23647
  ...accountId === null ? {} : { extraHeaders: [["chatgpt-account-id", accountId]] }
23345
23648
  });
@@ -23360,6 +23663,7 @@ var DISPATCH_REFRESH_LEAD_MS = 120000;
23360
23663
  var SCHEDULER_REFRESH_LEAD_MS = 300000;
23361
23664
  // packages/control/src/oauth/refresh.ts
23362
23665
  function createRefresher(deps) {
23666
+ const logger2 = deps.logger ?? noopLogger;
23363
23667
  const inFlight = new Map;
23364
23668
  async function run(credential) {
23365
23669
  const secrets = await credential.openForRefresh();
@@ -23367,11 +23671,20 @@ function createRefresher(deps) {
23367
23671
  throw new GatewayError("AUTH", `credential ${credential.id} has no refresh token`);
23368
23672
  }
23369
23673
  const provider = deps.providers[credential.provider];
23674
+ logger2.debug("refreshing credential", {
23675
+ provider: credential.provider,
23676
+ credentialId: credential.id
23677
+ });
23370
23678
  let result;
23371
23679
  try {
23372
23680
  result = await provider.refresh(secrets.refreshToken, { http: deps.http, now: deps.now }, credential.providerData);
23373
23681
  } catch (error51) {
23374
23682
  if (error51 instanceof GatewayError && error51.code === "AUTH") {
23683
+ logger2.warn("credential disabled: refresh token rejected", {
23684
+ provider: credential.provider,
23685
+ credentialId: credential.id,
23686
+ code: "AUTH"
23687
+ });
23375
23688
  await deps.store.credentials.update(credential.id, {
23376
23689
  enabled: false,
23377
23690
  disabledReason: "tokenRejected",
@@ -23385,6 +23698,10 @@ function createRefresher(deps) {
23385
23698
  accountEmail: result.accountEmail ?? credential.accountEmail,
23386
23699
  providerData: { ...credential.providerData, ...result.providerData }
23387
23700
  });
23701
+ logger2.debug("credential refreshed", {
23702
+ provider: credential.provider,
23703
+ credentialId: credential.id
23704
+ });
23388
23705
  return result.secrets;
23389
23706
  }
23390
23707
  return function refresh(credential) {
@@ -23424,9 +23741,15 @@ async function probe(deps, credential) {
23424
23741
  observedAt
23425
23742
  }));
23426
23743
  await deps.store.credentials.saveQuota(rows);
23744
+ (deps.logger ?? noopLogger).debug("quota snapshot written", {
23745
+ provider: credential.provider,
23746
+ credentialId: credential.id,
23747
+ count: rows.length
23748
+ });
23427
23749
  return rows;
23428
23750
  }
23429
23751
  async function poll(deps) {
23752
+ const logger2 = deps.logger ?? noopLogger;
23430
23753
  const now = deps.now();
23431
23754
  const credentials = (await deps.store.credentials.list()).filter((c) => c.enabled && c.authType === "oauth" && deps.providers[c.provider].usage !== undefined && (cooldowns.get(c.id) ?? 0) <= now);
23432
23755
  let written = 0;
@@ -23440,12 +23763,15 @@ async function poll(deps) {
23440
23763
  if (await probe(deps, credential) !== null)
23441
23764
  written += 1;
23442
23765
  } catch (error51) {
23443
- if (error51 instanceof GatewayError && error51.code === "RATE_LIMIT") {
23766
+ const rateLimited = error51 instanceof GatewayError && error51.code === "RATE_LIMIT";
23767
+ if (rateLimited) {
23444
23768
  cooldowns.set(credential.id, deps.now() + RATE_LIMIT_COOLDOWN_MS);
23445
23769
  }
23446
- console.warn("quota probe failed", {
23447
- credentialId: credential.id,
23770
+ logger2.warn(rateLimited ? "quota probe rate limited" : "quota probe failed", {
23448
23771
  provider: credential.provider,
23772
+ credentialId: credential.id,
23773
+ code: error51 instanceof GatewayError ? error51.code : "INTERNAL",
23774
+ ...rateLimited ? { retryAfterMs: RATE_LIMIT_COOLDOWN_MS } : {},
23449
23775
  reason: error51 instanceof Error ? error51.message : "unknown"
23450
23776
  });
23451
23777
  }
@@ -23463,6 +23789,43 @@ async function putSettings(store, input) {
23463
23789
  await store.config.putSettings(settings);
23464
23790
  return settings;
23465
23791
  }
23792
+ // packages/control/src/tail.ts
23793
+ import { closeSync, fstatSync, openSync, readSync, statSync } from "fs";
23794
+ var CHUNK = 64 * 1024;
23795
+ var MAX_BYTES = 8 * 1024 * 1024;
23796
+ function tailFile(path, lines) {
23797
+ let fd;
23798
+ try {
23799
+ fd = openSync(path, "r");
23800
+ } catch {
23801
+ return null;
23802
+ }
23803
+ try {
23804
+ const size = fstatSync(fd).size;
23805
+ if (size === 0)
23806
+ return "";
23807
+ const budget = Math.min(size, MAX_BYTES);
23808
+ let read = 0;
23809
+ let newlines = 0;
23810
+ const chunks = [];
23811
+ while (read < budget && newlines <= lines) {
23812
+ const span = Math.min(CHUNK, budget - read);
23813
+ const buffer = Buffer.allocUnsafe(span);
23814
+ const position = size - read - span;
23815
+ readSync(fd, buffer, 0, span, position);
23816
+ chunks.unshift(buffer);
23817
+ read += span;
23818
+ for (const byte of buffer)
23819
+ if (byte === 10)
23820
+ newlines++;
23821
+ }
23822
+ return Buffer.concat(chunks).toString("utf8");
23823
+ } catch {
23824
+ return null;
23825
+ } finally {
23826
+ closeSync(fd);
23827
+ }
23828
+ }
23466
23829
  // packages/control/src/usage.ts
23467
23830
  var MAX_LOG_LIMIT = 500;
23468
23831
  var DEFAULT_LOG_LIMIT = 100;
@@ -37903,7 +38266,7 @@ class ApiKeyRateLimiter {
37903
38266
  }
37904
38267
 
37905
38268
  // apps/gateway/src/dispatch/snapshotCache.ts
37906
- function createRoutingSnapshotCache(store) {
38269
+ function createRoutingSnapshotCache(store, logger2 = noopLogger) {
37907
38270
  let snapshot = null;
37908
38271
  let stale = true;
37909
38272
  let version2 = store.routing.version();
@@ -37942,6 +38305,7 @@ function createRoutingSnapshotCache(store) {
37942
38305
  return inFlight;
37943
38306
  const buildGeneration = generation;
37944
38307
  const buildVersion = currentVersion;
38308
+ logger2.debug("routing snapshot cache miss");
37945
38309
  inFlight = buildSnapshot(store, now).then(async (next) => {
37946
38310
  const completedVersion = store.routing.version();
37947
38311
  if (generation !== buildGeneration || completedVersion !== buildVersion) {
@@ -37961,6 +38325,28 @@ function createRoutingSnapshotCache(store) {
37961
38325
  }
37962
38326
 
37963
38327
  // apps/gateway/src/ingress/schemas.ts
38328
+ var cacheControlSchema = exports_external.object({
38329
+ type: exports_external.literal("ephemeral"),
38330
+ ttl: exports_external.enum(["5m", "1h"]).optional()
38331
+ });
38332
+ function looseCacheControl(value) {
38333
+ const parsed = cacheControlSchema.safeParse(value);
38334
+ return parsed.success ? irCacheControl(parsed.data) : {};
38335
+ }
38336
+ function irCacheControl(c) {
38337
+ if (c === undefined)
38338
+ return {};
38339
+ return { cacheControl: { type: c.type, ...c.ttl === undefined ? {} : { ttl: c.ttl } } };
38340
+ }
38341
+ function applyMessageCacheControl(blocks, value) {
38342
+ const control = looseCacheControl(value);
38343
+ if (control.cacheControl === undefined)
38344
+ return;
38345
+ const last = blocks.at(-1);
38346
+ if (last === undefined || last.type === "thinking" || cacheControlOf(last) !== undefined)
38347
+ return;
38348
+ Object.assign(last, control);
38349
+ }
37964
38350
  function parseOrThrow2(schema, body2) {
37965
38351
  const result = schema.safeParse(body2);
37966
38352
  if (result.success)
@@ -38040,6 +38426,7 @@ function apiErrorHandler(input) {
38040
38426
 
38041
38427
  // apps/gateway/src/routes/admin.ts
38042
38428
  function adminRoutes(deps) {
38429
+ const logger2 = deps.logger ?? noopLogger;
38043
38430
  return new Elysia().onError(apiErrorHandler).get("/api/status", async ({ request: request2 }) => {
38044
38431
  const token = readCookie(request2, ADMIN_COOKIE);
38045
38432
  return {
@@ -38049,7 +38436,9 @@ function adminRoutes(deps) {
38049
38436
  }).post("/api/setup", async ({ request: request2, set: set2 }) => {
38050
38437
  if (await deps.admin.isConfigured()) {
38051
38438
  set2.status = 409;
38052
- return { error: { code: "CONFLICT", message: "an admin password is already configured" } };
38439
+ return {
38440
+ error: { code: "CONFLICT", message: "an admin password is already configured" }
38441
+ };
38053
38442
  }
38054
38443
  const body2 = await readJsonRecord(request2);
38055
38444
  if (typeof body2?.password !== "string") {
@@ -38063,18 +38452,24 @@ function adminRoutes(deps) {
38063
38452
  }
38064
38453
  if (!created) {
38065
38454
  set2.status = 409;
38066
- return { error: { code: "CONFLICT", message: "an admin password is already configured" } };
38455
+ return {
38456
+ error: { code: "CONFLICT", message: "an admin password is already configured" }
38457
+ };
38067
38458
  }
38068
38459
  const token = await deps.admin.login(body2.password);
38069
38460
  if (token === null)
38070
38461
  throw new GatewayError("INTERNAL", "could not create admin session");
38462
+ logger2.info("admin setup completed");
38071
38463
  set2.headers["set-cookie"] = sessionCookie(request2, token, Math.floor(deps.sessionTtlMs / 1000));
38072
38464
  return { ok: true };
38073
38465
  }).post("/api/login", async ({ request: request2, set: set2 }) => {
38074
38466
  const body2 = await readJsonRecord(request2);
38075
38467
  const token = typeof body2?.password === "string" ? await deps.admin.login(body2.password) : null;
38076
- if (token === null)
38468
+ if (token === null) {
38469
+ logger2.info("admin login failed", { reason: "invalid credentials" });
38077
38470
  throw new GatewayError("AUTH", "invalid password");
38471
+ }
38472
+ logger2.info("admin login succeeded");
38078
38473
  set2.headers["set-cookie"] = sessionCookie(request2, token, Math.floor(deps.sessionTtlMs / 1000));
38079
38474
  return { ok: true };
38080
38475
  }).post("/api/logout", async ({ request: request2, set: set2 }) => {
@@ -38142,6 +38537,17 @@ function adminRoutes(deps) {
38142
38537
  }).get("/api/logs", async ({ request: request2, query }) => {
38143
38538
  await requireAdmin(request2, deps.admin);
38144
38539
  return { logs: await recentLogs(deps.store, query.limit) };
38540
+ }).get("/api/console", async ({ request: request2, query }) => {
38541
+ await requireAdmin(request2, deps.admin);
38542
+ if (deps.console === undefined)
38543
+ return { source: "none", lines: [] };
38544
+ const level = parseLogLevel(query.level);
38545
+ const since = Number(query.since);
38546
+ return readConsole(deps.console.deps, deps.console.source, {
38547
+ lines: consoleLimit(query.lines),
38548
+ ...level === null ? {} : { level },
38549
+ ...Number.isFinite(since) && since > 0 ? { since } : {}
38550
+ });
38145
38551
  });
38146
38552
  }
38147
38553
 
@@ -38240,41 +38646,47 @@ function newPendingRequestLog(input) {
38240
38646
  status: 0
38241
38647
  };
38242
38648
  }
38243
- function report(what, requestId, error51) {
38244
- console.error(what, {
38649
+ function report(logger2, what, requestId, error51) {
38650
+ logger2.warn(what, {
38245
38651
  requestId,
38246
38652
  reason: error51 instanceof Error ? error51.message : "unknown"
38247
38653
  });
38248
38654
  }
38249
- async function beginLog(store, log, keyId) {
38655
+ async function beginLog(store, log, keyId, logger2 = noopLogger) {
38250
38656
  try {
38251
38657
  await store.usage.begin({ ...log, state: "pending", apiKeyId: keyId });
38252
38658
  } catch (error51) {
38253
- report("failed to record request start", log.id, error51);
38659
+ report(logger2, "failed to record request start", log.id, error51);
38254
38660
  }
38255
38661
  }
38256
- async function routeLog(store, requestId, target) {
38662
+ async function routeLog(store, requestId, target, logger2 = noopLogger) {
38257
38663
  try {
38258
38664
  await store.usage.route(requestId, target);
38259
38665
  } catch (error51) {
38260
- report("failed to record request route", requestId, error51);
38666
+ report(logger2, "failed to record request route", requestId, error51);
38261
38667
  }
38262
38668
  }
38263
- async function finishLog(store, log, keyId) {
38669
+ async function finishLog(store, log, keyId, logger2 = noopLogger) {
38264
38670
  try {
38265
38671
  await store.usage.append({ ...log, apiKeyId: keyId });
38266
38672
  } catch (error51) {
38267
- report("failed to persist request log", log.id, error51);
38673
+ report(logger2, "failed to persist request log", log.id, error51);
38268
38674
  }
38269
38675
  }
38270
38676
 
38271
38677
  // apps/gateway/src/dispatch/attempt.ts
38272
38678
  async function attempt(opts) {
38273
38679
  const { candidate, adapter, http, now, signal, refresh, refreshLeadMs } = opts;
38680
+ const logger2 = opts.logger ?? noopLogger;
38274
38681
  const credential = candidate.credential;
38275
38682
  let secrets = opts.secrets ?? await credential.openForInference();
38276
38683
  const stale = opts.secrets === undefined && credential.authType === "oauth" && credential.expiresAt !== null && credential.expiresAt - refreshLeadMs <= now;
38277
38684
  if (stale) {
38685
+ logger2.debug("preemptive credential refresh", {
38686
+ requestId: opts.requestId,
38687
+ provider: credential.provider,
38688
+ credentialId: credential.id
38689
+ });
38278
38690
  if (!credential.hasRefreshToken) {
38279
38691
  throw new GatewayError("AUTH", "credential expired with no refresh token", {
38280
38692
  provider: credential.provider
@@ -38320,8 +38732,35 @@ function classify(error51) {
38320
38732
  return { code: "INTERNAL" };
38321
38733
  }
38322
38734
 
38735
+ // apps/gateway/src/dispatch/price.ts
38736
+ var WRITE_OVER_INPUT = {
38737
+ anthropic: { fiveMinute: 1.25, oneHour: 2 },
38738
+ openai: { fiveMinute: 0, oneHour: 0 },
38739
+ kimi: { fiveMinute: 0, oneHour: 0 }
38740
+ };
38741
+ var READ_OVER_INPUT = 0.1;
38742
+ function splitWrites(usage) {
38743
+ const { cacheWrite5mTokens: five, cacheWrite1hTokens: hour, cacheWriteTokens: total } = usage;
38744
+ if (five !== undefined && hour !== undefined)
38745
+ return { fiveMinute: five, oneHour: hour };
38746
+ if (five !== undefined)
38747
+ return { fiveMinute: five, oneHour: Math.max(0, total - five) };
38748
+ if (hour !== undefined)
38749
+ return { fiveMinute: Math.max(0, total - hour), oneHour: hour };
38750
+ return { fiveMinute: total, oneHour: 0 };
38751
+ }
38752
+ function priceOf(prices, usage, provider) {
38753
+ const fallback = WRITE_OVER_INPUT[provider];
38754
+ const readRate = prices.cacheRead ?? prices.input * READ_OVER_INPUT;
38755
+ const write5mRate = prices.cacheWrite5m ?? prices.input * fallback.fiveMinute;
38756
+ const write1hRate = prices.cacheWrite1h ?? prices.input * fallback.oneHour;
38757
+ const writes = splitWrites(usage);
38758
+ return (usage.inputTokens * prices.input + usage.outputTokens * prices.output + usage.cacheReadTokens * readRate + writes.fiveMinute * write5mRate + writes.oneHour * write1hRate) / 1e6;
38759
+ }
38760
+
38323
38761
  // apps/gateway/src/dispatch/index.ts
38324
38762
  async function dispatch(request2, deps, signal, requestId) {
38763
+ const logger2 = deps.logger ?? noopLogger;
38325
38764
  const startedAt = deps.now();
38326
38765
  const snapshot = await deps.snapshots.get(startedAt);
38327
38766
  const deadlineAt = startedAt + snapshot.settings.requestDeadlineMs;
@@ -38375,8 +38814,20 @@ async function dispatch(request2, deps, signal, requestId) {
38375
38814
  now: startedAt,
38376
38815
  rand: deps.rand()
38377
38816
  });
38817
+ logger2.debug("routing candidates ranked", {
38818
+ requestId,
38819
+ requestedModel: request2.model,
38820
+ model: candidates[0]?.target.model,
38821
+ credentialId: candidates[0]?.credential.id,
38822
+ count: candidates.length
38823
+ });
38378
38824
  for (const e of excluded) {
38379
38825
  log.degradations.push(`excluded:${e.credentialId}:${e.reason}`);
38826
+ logger2.debug("routing candidate excluded", {
38827
+ requestId,
38828
+ credentialId: e.credentialId,
38829
+ reason: e.reason
38830
+ });
38380
38831
  }
38381
38832
  if (candidates.length === 0) {
38382
38833
  clearDeadline();
@@ -38407,6 +38858,13 @@ async function dispatch(request2, deps, signal, requestId) {
38407
38858
  model: candidate.target.model,
38408
38859
  credentialId: candidate.credential.id
38409
38860
  });
38861
+ logger2.debug("attempt started", {
38862
+ requestId,
38863
+ provider: candidate.target.provider,
38864
+ model: candidate.target.model,
38865
+ credentialId: candidate.credential.id,
38866
+ attempt: i + 1
38867
+ });
38410
38868
  log.inputTokens = 0;
38411
38869
  log.outputTokens = 0;
38412
38870
  log.cacheReadTokens = 0;
@@ -38428,7 +38886,9 @@ async function dispatch(request2, deps, signal, requestId) {
38428
38886
  signal: dispatchSignal,
38429
38887
  refresh: (credential) => waitForCancellation(deps.refresh(credential), dispatchSignal),
38430
38888
  refreshLeadMs: DISPATCH_REFRESH_LEAD_MS,
38431
- ...retrySecrets === undefined ? {} : { secrets: retrySecrets }
38889
+ ...retrySecrets === undefined ? {} : { secrets: retrySecrets },
38890
+ logger: logger2,
38891
+ requestId
38432
38892
  }), dispatchSignal);
38433
38893
  for (const d of result.degradations)
38434
38894
  log.degradations.push(d);
@@ -38438,6 +38898,14 @@ async function dispatch(request2, deps, signal, requestId) {
38438
38898
  if (event.type === "blockDelta" && !committed) {
38439
38899
  committed = true;
38440
38900
  log.ttftMs = deps.now() - startedAt;
38901
+ logger2.debug("stream committed", {
38902
+ requestId,
38903
+ provider: candidate.target.provider,
38904
+ model: candidate.target.model,
38905
+ credentialId: candidate.credential.id,
38906
+ attempt: i + 1,
38907
+ ttftMs: log.ttftMs
38908
+ });
38441
38909
  for (const buffered of pending)
38442
38910
  yield buffered;
38443
38911
  pending.length = 0;
@@ -38448,7 +38916,7 @@ async function dispatch(request2, deps, signal, requestId) {
38448
38916
  log.outputTokens = event.usage.outputTokens;
38449
38917
  log.cacheReadTokens = event.usage.cacheReadTokens;
38450
38918
  log.cacheWriteTokens = event.usage.cacheWriteTokens;
38451
- log.costUsd = priceOf(candidate, event.usage);
38919
+ log.costUsd = priceOf(candidate.target.costPerMTok, event.usage, candidate.target.provider);
38452
38920
  }
38453
38921
  if (event.type === "error") {
38454
38922
  terminal = true;
@@ -38502,6 +38970,14 @@ async function dispatch(request2, deps, signal, requestId) {
38502
38970
  lastError = retryAfterMs === undefined ? new GatewayError(code2, message) : new GatewayError(code2, message, { retryAfterMs });
38503
38971
  if (code2 === "AUTH" && !committed && !authRefreshRetried && !preemptiveRefreshRequired && candidate.credential.authType === "oauth" && candidate.credential.hasRefreshToken) {
38504
38972
  authRefreshRetried = true;
38973
+ logger2.warn("attempt authentication failed; refreshing credential", {
38974
+ requestId,
38975
+ provider: candidate.target.provider,
38976
+ model: candidate.target.model,
38977
+ credentialId: candidate.credential.id,
38978
+ attempt: i + 1,
38979
+ code: code2
38980
+ });
38505
38981
  try {
38506
38982
  retrySecrets = await waitForCancellation(deps.refresh(candidate.credential), dispatchSignal);
38507
38983
  continue;
@@ -38523,6 +38999,18 @@ async function dispatch(request2, deps, signal, requestId) {
38523
38999
  ...failure.retryAfterMs === undefined ? {} : { retryAfterMs: failure.retryAfterMs },
38524
39000
  jitter: deps.rand()
38525
39001
  }));
39002
+ if (!committed && RETRYABLE[failure.code] && i + 1 < maxAttempts) {
39003
+ logger2.warn("attempt failed; retrying", {
39004
+ requestId,
39005
+ provider: candidate.target.provider,
39006
+ model: candidate.target.model,
39007
+ credentialId: candidate.credential.id,
39008
+ attempt: i + 1,
39009
+ code: failure.code,
39010
+ retryable: true,
39011
+ retryAfterMs: failure.retryAfterMs
39012
+ });
39013
+ }
38526
39014
  if (committed) {
38527
39015
  log.status = HTTP_STATUS[failure.code];
38528
39016
  log.errorCode = failure.code;
@@ -38559,11 +39047,6 @@ async function dispatch(request2, deps, signal, requestId) {
38559
39047
  }
38560
39048
  return { events: run(), log: () => log };
38561
39049
  }
38562
- function priceOf(candidate, usage) {
38563
- const p = candidate.target.costPerMTok;
38564
- const cacheRate = p.cacheRead ?? p.input * 0.1;
38565
- return (usage.inputTokens * p.input + usage.outputTokens * p.output + usage.cacheReadTokens * cacheRate) / 1e6;
38566
- }
38567
39050
  function waitForCancellation(promise2, signal) {
38568
39051
  if (signal.aborted)
38569
39052
  return Promise.reject(signal.reason);
@@ -38610,6 +39093,9 @@ var frame = (event, data) => ({
38610
39093
  });
38611
39094
  async function* anthropicStream(events, requestId) {
38612
39095
  let model = "";
39096
+ const suppressed = new Set;
39097
+ const outIndex = new Map;
39098
+ let nextOutIndex = 0;
38613
39099
  for await (const event of events) {
38614
39100
  switch (event.type) {
38615
39101
  case "start":
@@ -38624,32 +39110,50 @@ async function* anthropicStream(events, requestId) {
38624
39110
  content: [],
38625
39111
  stop_reason: null,
38626
39112
  stop_sequence: null,
38627
- usage: { input_tokens: 0, output_tokens: 0 }
39113
+ usage: {
39114
+ input_tokens: 0,
39115
+ output_tokens: 0,
39116
+ cache_read_input_tokens: 0,
39117
+ cache_creation_input_tokens: 0
39118
+ }
38628
39119
  }
38629
39120
  });
38630
39121
  break;
38631
39122
  case "blockStart": {
38632
39123
  const b = event.block;
39124
+ if (b.type === "thinking" && b.signed !== true) {
39125
+ suppressed.add(event.index);
39126
+ break;
39127
+ }
38633
39128
  const content_block = b.type === "text" ? { type: "text", text: "" } : b.type === "thinking" ? { type: "thinking", thinking: "" } : { type: "tool_use", id: b.id, name: b.name, input: {} };
39129
+ const index = nextOutIndex++;
39130
+ outIndex.set(event.index, index);
38634
39131
  yield frame("content_block_start", {
38635
39132
  type: "content_block_start",
38636
- index: event.index,
39133
+ index,
38637
39134
  content_block
38638
39135
  });
38639
39136
  break;
38640
39137
  }
38641
39138
  case "blockDelta": {
39139
+ if (suppressed.has(event.index))
39140
+ break;
38642
39141
  const d = event.delta;
38643
39142
  const delta2 = d.type === "text" ? { type: "text_delta", text: d.text } : d.type === "thinking" ? { type: "thinking_delta", thinking: d.text } : d.type === "thinkingSignature" ? { type: "signature_delta", signature: d.signature } : { type: "input_json_delta", partial_json: d.partial };
38644
39143
  yield frame("content_block_delta", {
38645
39144
  type: "content_block_delta",
38646
- index: event.index,
39145
+ index: outIndex.get(event.index) ?? event.index,
38647
39146
  delta: delta2
38648
39147
  });
38649
39148
  break;
38650
39149
  }
38651
39150
  case "blockEnd":
38652
- yield frame("content_block_stop", { type: "content_block_stop", index: event.index });
39151
+ if (suppressed.has(event.index))
39152
+ break;
39153
+ yield frame("content_block_stop", {
39154
+ type: "content_block_stop",
39155
+ index: outIndex.get(event.index) ?? event.index
39156
+ });
38653
39157
  break;
38654
39158
  case "end":
38655
39159
  yield frame("message_delta", {
@@ -38657,7 +39161,9 @@ async function* anthropicStream(events, requestId) {
38657
39161
  delta: { stop_reason: STOP_REASON2[event.stopReason], stop_sequence: null },
38658
39162
  usage: {
38659
39163
  input_tokens: event.usage.inputTokens,
38660
- output_tokens: event.usage.outputTokens
39164
+ output_tokens: event.usage.outputTokens,
39165
+ cache_read_input_tokens: event.usage.cacheReadTokens,
39166
+ cache_creation_input_tokens: event.usage.cacheWriteTokens
38661
39167
  }
38662
39168
  });
38663
39169
  yield frame("message_stop", { type: "message_stop" });
@@ -38677,7 +39183,7 @@ function anthropicResponse(collected, requestId) {
38677
39183
  type: "message",
38678
39184
  role: "assistant",
38679
39185
  model: collected.model,
38680
- content: collected.content.map((b) => {
39186
+ content: collected.content.filter((b) => b.type !== "thinking" || b.signature !== undefined && b.signature !== "").map((b) => {
38681
39187
  switch (b.type) {
38682
39188
  case "text":
38683
39189
  return { type: "text", text: b.text };
@@ -38693,7 +39199,9 @@ function anthropicResponse(collected, requestId) {
38693
39199
  stop_sequence: null,
38694
39200
  usage: {
38695
39201
  input_tokens: collected.usage.inputTokens,
38696
- output_tokens: collected.usage.outputTokens
39202
+ output_tokens: collected.usage.outputTokens,
39203
+ cache_read_input_tokens: collected.usage.cacheReadTokens,
39204
+ cache_creation_input_tokens: collected.usage.cacheWriteTokens
38697
39205
  }
38698
39206
  };
38699
39207
  }
@@ -38702,6 +39210,15 @@ function anthropicErrorBody(code, message) {
38702
39210
  }
38703
39211
 
38704
39212
  // apps/gateway/src/egress/openai.ts
39213
+ function usageBody(usage) {
39214
+ const prompt = promptTokens(usage);
39215
+ return {
39216
+ prompt_tokens: prompt,
39217
+ completion_tokens: usage.outputTokens,
39218
+ total_tokens: prompt + usage.outputTokens,
39219
+ prompt_tokens_details: { cached_tokens: usage.cacheReadTokens }
39220
+ };
39221
+ }
38705
39222
  var FINISH2 = {
38706
39223
  endTurn: "stop",
38707
39224
  maxTokens: "length",
@@ -38799,11 +39316,7 @@ async function* openaiStream(events, requestId, created) {
38799
39316
  break;
38800
39317
  case "end":
38801
39318
  completed = true;
38802
- yield emit(chunk(requestId, created, model, { index: 0, delta: {}, finish_reason: FINISH2[event.stopReason] }, {
38803
- prompt_tokens: event.usage.inputTokens,
38804
- completion_tokens: event.usage.outputTokens,
38805
- total_tokens: event.usage.inputTokens + event.usage.outputTokens
38806
- }));
39319
+ yield emit(chunk(requestId, created, model, { index: 0, delta: {}, finish_reason: FINISH2[event.stopReason] }, usageBody(event.usage)));
38807
39320
  break;
38808
39321
  case "error": {
38809
39322
  const e = ERROR_TYPE3[event.code];
@@ -38840,11 +39353,7 @@ function openaiResponse(collected, requestId, created) {
38840
39353
  finish_reason: FINISH2[collected.stopReason]
38841
39354
  }
38842
39355
  ],
38843
- usage: {
38844
- prompt_tokens: collected.usage.inputTokens,
38845
- completion_tokens: collected.usage.outputTokens,
38846
- total_tokens: collected.usage.inputTokens + collected.usage.outputTokens
38847
- }
39356
+ usage: usageBody(collected.usage)
38848
39357
  };
38849
39358
  }
38850
39359
  function openaiErrorBody(code, message) {
@@ -38853,14 +39362,19 @@ function openaiErrorBody(code, message) {
38853
39362
  }
38854
39363
 
38855
39364
  // apps/gateway/src/ingress/anthropic.ts
38856
- var textBlock = exports_external.object({ type: exports_external.literal("text"), text: exports_external.string() });
39365
+ var textBlock = exports_external.object({
39366
+ type: exports_external.literal("text"),
39367
+ text: exports_external.string(),
39368
+ cache_control: cacheControlSchema.optional()
39369
+ });
38857
39370
  var imageBlock = exports_external.object({
38858
39371
  type: exports_external.literal("image"),
38859
39372
  source: exports_external.object({
38860
39373
  type: exports_external.literal("base64"),
38861
39374
  media_type: exports_external.string(),
38862
39375
  data: exports_external.string()
38863
- })
39376
+ }),
39377
+ cache_control: cacheControlSchema.optional()
38864
39378
  });
38865
39379
  var thinkingBlock = exports_external.object({
38866
39380
  type: exports_external.literal("thinking"),
@@ -38871,13 +39385,15 @@ var toolUseBlock = exports_external.object({
38871
39385
  type: exports_external.literal("tool_use"),
38872
39386
  id: exports_external.string(),
38873
39387
  name: exports_external.string(),
38874
- input: exports_external.record(exports_external.string(), exports_external.unknown()).default({})
39388
+ input: exports_external.record(exports_external.string(), exports_external.unknown()).default({}),
39389
+ cache_control: cacheControlSchema.optional()
38875
39390
  });
38876
39391
  var toolResultBlock = exports_external.object({
38877
39392
  type: exports_external.literal("tool_result"),
38878
39393
  tool_use_id: exports_external.string(),
38879
39394
  content: exports_external.union([exports_external.string(), exports_external.array(exports_external.unknown())]).optional(),
38880
- is_error: exports_external.boolean().optional()
39395
+ is_error: exports_external.boolean().optional(),
39396
+ cache_control: cacheControlSchema.optional()
38881
39397
  });
38882
39398
  var block = exports_external.discriminatedUnion("type", [
38883
39399
  textBlock,
@@ -38901,7 +39417,8 @@ var schema = exports_external.object({
38901
39417
  tools: exports_external.array(exports_external.object({
38902
39418
  name: exports_external.string(),
38903
39419
  description: exports_external.string().optional(),
38904
- input_schema: exports_external.record(exports_external.string(), exports_external.unknown())
39420
+ input_schema: exports_external.record(exports_external.string(), exports_external.unknown()),
39421
+ cache_control: cacheControlSchema.optional()
38905
39422
  })).optional(),
38906
39423
  tool_choice: exports_external.union([
38907
39424
  exports_external.object({ type: exports_external.enum(["auto", "any", "none"]) }),
@@ -38945,9 +39462,14 @@ function flattenToolResult(content) {
38945
39462
  function toIrBlock(b) {
38946
39463
  switch (b.type) {
38947
39464
  case "text":
38948
- return { type: "text", text: b.text };
39465
+ return { type: "text", text: b.text, ...irCacheControl(b.cache_control) };
38949
39466
  case "image":
38950
- return { type: "image", mediaType: b.source.media_type, data: b.source.data };
39467
+ return {
39468
+ type: "image",
39469
+ mediaType: b.source.media_type,
39470
+ data: b.source.data,
39471
+ ...irCacheControl(b.cache_control)
39472
+ };
38951
39473
  case "thinking":
38952
39474
  return {
38953
39475
  type: "thinking",
@@ -38955,13 +39477,20 @@ function toIrBlock(b) {
38955
39477
  ...b.signature !== undefined && { signature: b.signature }
38956
39478
  };
38957
39479
  case "tool_use":
38958
- return { type: "toolUse", id: b.id, name: b.name, input: b.input };
39480
+ return {
39481
+ type: "toolUse",
39482
+ id: b.id,
39483
+ name: b.name,
39484
+ input: b.input,
39485
+ ...irCacheControl(b.cache_control)
39486
+ };
38959
39487
  case "tool_result":
38960
39488
  return {
38961
39489
  type: "toolResult",
38962
39490
  toolUseId: b.tool_use_id,
38963
39491
  content: flattenToolResult(b.content),
38964
- isError: b.is_error ?? false
39492
+ isError: b.is_error ?? false,
39493
+ ...irCacheControl(b.cache_control)
38965
39494
  };
38966
39495
  }
38967
39496
  }
@@ -39008,7 +39537,11 @@ function parseAnthropicRequest(body2, headers) {
39008
39537
  role: m.role,
39009
39538
  content: typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content.map(toIrBlock)
39010
39539
  }));
39011
- const system = parsed.system === undefined ? undefined : typeof parsed.system === "string" ? [{ type: "text", text: parsed.system }] : parsed.system.map((b) => ({ type: "text", text: b.text }));
39540
+ const system = parsed.system === undefined ? undefined : typeof parsed.system === "string" ? [{ type: "text", text: parsed.system }] : parsed.system.map((b) => ({
39541
+ type: "text",
39542
+ text: b.text,
39543
+ ...irCacheControl(b.cache_control)
39544
+ }));
39012
39545
  const request2 = {
39013
39546
  model: parsed.model,
39014
39547
  messages,
@@ -39026,7 +39559,8 @@ function parseAnthropicRequest(body2, headers) {
39026
39559
  request2.tools = parsed.tools.map((t2) => ({
39027
39560
  name: t2.name,
39028
39561
  ...t2.description !== undefined && { description: t2.description },
39029
- inputSchema: t2.input_schema
39562
+ inputSchema: t2.input_schema,
39563
+ ...irCacheControl(t2.cache_control)
39030
39564
  }));
39031
39565
  }
39032
39566
  if (parsed.tool_choice !== undefined)
@@ -39048,8 +39582,16 @@ function parseAnthropicRequest(body2, headers) {
39048
39582
 
39049
39583
  // apps/gateway/src/ingress/openai.ts
39050
39584
  var part = exports_external.discriminatedUnion("type", [
39051
- exports_external.object({ type: exports_external.literal("text"), text: exports_external.string() }),
39052
- exports_external.object({ type: exports_external.literal("image_url"), image_url: exports_external.object({ url: exports_external.string() }) })
39585
+ exports_external.object({
39586
+ type: exports_external.literal("text"),
39587
+ text: exports_external.string(),
39588
+ cache_control: exports_external.unknown().optional()
39589
+ }),
39590
+ exports_external.object({
39591
+ type: exports_external.literal("image_url"),
39592
+ image_url: exports_external.object({ url: exports_external.string() }),
39593
+ cache_control: exports_external.unknown().optional()
39594
+ })
39053
39595
  ]);
39054
39596
  var toolCall = exports_external.object({
39055
39597
  id: exports_external.string(),
@@ -39060,7 +39602,8 @@ var message2 = exports_external.object({
39060
39602
  role: exports_external.enum(["system", "developer", "user", "assistant", "tool"]),
39061
39603
  content: exports_external.union([exports_external.string(), exports_external.array(part), exports_external.null()]).optional(),
39062
39604
  tool_calls: exports_external.array(toolCall).optional(),
39063
- tool_call_id: exports_external.string().optional()
39605
+ tool_call_id: exports_external.string().optional(),
39606
+ cache_control: exports_external.unknown().optional()
39064
39607
  });
39065
39608
  var schema2 = exports_external.object({
39066
39609
  model: exports_external.string().min(1),
@@ -39075,8 +39618,10 @@ var schema2 = exports_external.object({
39075
39618
  function: exports_external.object({
39076
39619
  name: exports_external.string(),
39077
39620
  description: exports_external.string().optional(),
39078
- parameters: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
39079
- })
39621
+ parameters: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
39622
+ cache_control: exports_external.unknown().optional()
39623
+ }),
39624
+ cache_control: exports_external.unknown().optional()
39080
39625
  })).optional(),
39081
39626
  tool_choice: exports_external.union([
39082
39627
  exports_external.enum(["auto", "none", "required"]),
@@ -39114,11 +39659,15 @@ function contentBlocks(content) {
39114
39659
  return [];
39115
39660
  return content.map((p) => {
39116
39661
  if (p.type === "text")
39117
- return { type: "text", text: p.text };
39662
+ return { type: "text", text: p.text, ...looseCacheControl(p.cache_control) };
39118
39663
  const { mediaType, data } = parseDataUrl(p.image_url.url);
39119
- return { type: "image", mediaType, data };
39664
+ return { type: "image", mediaType, data, ...looseCacheControl(p.cache_control) };
39120
39665
  });
39121
39666
  }
39667
+ function toolCacheControl(outer, inner) {
39668
+ const parsed = looseCacheControl(outer);
39669
+ return parsed.cacheControl === undefined ? looseCacheControl(inner) : parsed;
39670
+ }
39122
39671
  function toIrToolChoice2(c) {
39123
39672
  if (typeof c === "string") {
39124
39673
  if (c === "required")
@@ -39136,7 +39685,9 @@ function parseOpenAIRequest(body2) {
39136
39685
  const messages = [];
39137
39686
  for (const m of parsed.messages) {
39138
39687
  if (m.role === "system" || m.role === "developer") {
39139
- system.push(...contentBlocks(m.content));
39688
+ const blocks = contentBlocks(m.content);
39689
+ applyMessageCacheControl(blocks, m.cache_control);
39690
+ system.push(...blocks);
39140
39691
  continue;
39141
39692
  }
39142
39693
  if (m.role === "tool") {
@@ -39150,7 +39701,8 @@ function parseOpenAIRequest(body2) {
39150
39701
  type: "toolResult",
39151
39702
  toolUseId: m.tool_call_id,
39152
39703
  content: typeof m.content === "string" ? m.content : "",
39153
- isError: false
39704
+ isError: false,
39705
+ ...looseCacheControl(m.cache_control)
39154
39706
  }
39155
39707
  ]
39156
39708
  });
@@ -39165,6 +39717,7 @@ function parseOpenAIRequest(body2) {
39165
39717
  input: parseArguments(call.function.arguments)
39166
39718
  });
39167
39719
  }
39720
+ applyMessageCacheControl(content, m.cache_control);
39168
39721
  if (content.length > 0)
39169
39722
  messages.push({ role: m.role, content });
39170
39723
  }
@@ -39190,7 +39743,8 @@ function parseOpenAIRequest(body2) {
39190
39743
  request2.tools = parsed.tools.map((t2) => ({
39191
39744
  name: t2.function.name,
39192
39745
  ...t2.function.description !== undefined && { description: t2.function.description },
39193
- inputSchema: t2.function.parameters ?? { type: "object" }
39746
+ inputSchema: t2.function.parameters ?? { type: "object" },
39747
+ ...toolCacheControl(t2.cache_control, t2.function.cache_control)
39194
39748
  }));
39195
39749
  }
39196
39750
  if (parsed.tool_choice !== undefined)
@@ -39203,6 +39757,85 @@ function parseOpenAIRequest(body2) {
39203
39757
  return validateRequest(request2);
39204
39758
  }
39205
39759
 
39760
+ // apps/gateway/src/routes/models.ts
39761
+ 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
+ function describeModel(model, credentials) {
39815
+ const limits = limitsOf(model, servingAuths(credentials));
39816
+ return {
39817
+ id: model.id,
39818
+ object: "model",
39819
+ type: "model",
39820
+ display_name: displayName(model),
39821
+ created: 0,
39822
+ created_at: CREATED_AT,
39823
+ owned_by: "omnigateway",
39824
+ ...limits.contextWindow === undefined ? {} : { max_input_tokens: limits.contextWindow },
39825
+ ...limits.maxOutputTokens === undefined ? {} : { max_tokens: limits.maxOutputTokens }
39826
+ };
39827
+ }
39828
+ function modelListBody(models, credentials) {
39829
+ const data = models.map((model) => describeModel(model, credentials));
39830
+ return {
39831
+ object: "list",
39832
+ data,
39833
+ has_more: false,
39834
+ first_id: data[0]?.id ?? null,
39835
+ last_id: data[data.length - 1]?.id ?? null
39836
+ };
39837
+ }
39838
+
39206
39839
  // apps/gateway/src/routes/proxy.ts
39207
39840
  var KEEPALIVE_MS = 1e4;
39208
39841
  var KEEPALIVE = Symbol("keepalive");
@@ -39234,9 +39867,9 @@ function asGatewayError(error51) {
39234
39867
  function sseResponse(frames, onDone, keepaliveMs) {
39235
39868
  const encoder2 = new TextEncoder;
39236
39869
  let done = null;
39237
- const runOnce = () => {
39870
+ const runOnce = (cancelled = false) => {
39238
39871
  if (done === null)
39239
- done = onDone();
39872
+ done = onDone(cancelled);
39240
39873
  return done;
39241
39874
  };
39242
39875
  let inflight = null;
@@ -39269,7 +39902,7 @@ data: ${data}
39269
39902
  },
39270
39903
  async cancel() {
39271
39904
  await frames.return(undefined);
39272
- await runOnce();
39905
+ await runOnce(true);
39273
39906
  }
39274
39907
  });
39275
39908
  return new Response(stream2, { headers: SSE_HEADERS });
@@ -39280,9 +39913,34 @@ async function handle(deps, rateLimiter, surface, request2) {
39280
39913
  let keyId = null;
39281
39914
  let requestedModel = "";
39282
39915
  try {
39283
- const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
39916
+ let key;
39917
+ try {
39918
+ key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
39919
+ } catch (error51) {
39920
+ if (error51 instanceof GatewayError && error51.code === "AUTH") {
39921
+ deps.logger.warn("authentication rejected", {
39922
+ requestId,
39923
+ surface,
39924
+ reason: "invalid credentials"
39925
+ });
39926
+ }
39927
+ throw error51;
39928
+ }
39284
39929
  keyId = key.id;
39285
- rateLimiter.consume(key.id, key.rateLimitPerMin);
39930
+ try {
39931
+ rateLimiter.consume(key.id, key.rateLimitPerMin);
39932
+ } catch (error51) {
39933
+ if (error51 instanceof GatewayError && error51.code === "RATE_LIMIT") {
39934
+ deps.logger.warn("rate limit rejected", {
39935
+ requestId,
39936
+ surface,
39937
+ apiKeyId: key.id,
39938
+ code: error51.code,
39939
+ retryAfterMs: error51.retryAfterMs
39940
+ });
39941
+ }
39942
+ throw error51;
39943
+ }
39286
39944
  const body2 = await request2.json();
39287
39945
  const chatRequest = surface === "anthropic" ? parseAnthropicRequest(body2, request2.headers) : parseOpenAIRequest(body2);
39288
39946
  if (key.modelAllowlist !== null && !key.modelAllowlist.includes(chatRequest.model)) {
@@ -39294,7 +39952,7 @@ async function handle(deps, rateLimiter, surface, request2) {
39294
39952
  ...deps,
39295
39953
  async onRoute(target) {
39296
39954
  if (began) {
39297
- await routeLog(deps.store, requestId, target);
39955
+ await routeLog(deps.store, requestId, target, deps.logger);
39298
39956
  return;
39299
39957
  }
39300
39958
  began = true;
@@ -39306,10 +39964,19 @@ async function handle(deps, rateLimiter, surface, request2) {
39306
39964
  resolvedModel: target.model,
39307
39965
  credentialId: target.credentialId
39308
39966
  });
39309
- await beginLog(deps.store, log2, keyId);
39967
+ await beginLog(deps.store, log2, keyId, deps.logger);
39310
39968
  }
39311
39969
  }, request2.signal, requestId);
39312
- const log = () => finishLog(deps.store, outcome.log(), keyId);
39970
+ const log = async (cancelled = false) => {
39971
+ const completed = outcome.log();
39972
+ const wasCancelled = cancelled || request2.signal.aborted;
39973
+ if (wasCancelled && completed.status === 0) {
39974
+ completed.status = 499;
39975
+ completed.errorCode = "interrupted";
39976
+ completed.durationMs = deps.now() - startedAt;
39977
+ }
39978
+ await finishLog(deps.store, completed, keyId, deps.logger);
39979
+ };
39313
39980
  if (chatRequest.stream) {
39314
39981
  const frames = surface === "anthropic" ? anthropicStream(outcome.events, requestId) : openaiStream(outcome.events, requestId, Math.floor(deps.now() / 1000));
39315
39982
  return sseResponse(frames, log, deps.keepaliveMs);
@@ -39329,41 +39996,55 @@ async function handle(deps, rateLimiter, surface, request2) {
39329
39996
  });
39330
39997
  } catch (error51) {
39331
39998
  const gatewayError = asGatewayError(error51);
39332
- await finishLog(deps.store, newCompletedRequestLog(requestId, startedAt, {
39999
+ const completed = newCompletedRequestLog(requestId, startedAt, {
39333
40000
  requestedModel,
39334
40001
  status: HTTP_STATUS[gatewayError.code],
39335
- errorCode: gatewayError.code
39336
- }), keyId);
40002
+ errorCode: gatewayError.code,
40003
+ durationMs: deps.now() - startedAt
40004
+ });
40005
+ await finishLog(deps.store, completed, keyId, deps.logger);
40006
+ deps.logger.warn("request rejected", {
40007
+ requestId,
40008
+ surface,
40009
+ status: completed.status,
40010
+ code: gatewayError.code,
40011
+ reason: gatewayError.message
40012
+ });
39337
40013
  return errorResponse(surface, gatewayError.code, gatewayError.message);
39338
40014
  }
39339
40015
  }
39340
40016
  function proxyRoutes(deps) {
39341
40017
  const rateLimiter = deps.rateLimiter ?? new ApiKeyRateLimiter(deps.now);
40018
+ const logger2 = deps.logger ?? noopLogger;
39342
40019
  const dispatchDeps = {
39343
40020
  ...deps,
39344
- snapshots: deps.snapshots ?? createRoutingSnapshotCache(deps.store),
40021
+ logger: logger2,
40022
+ snapshots: deps.snapshots ?? createRoutingSnapshotCache(deps.store, logger2),
39345
40023
  keepaliveMs: deps.keepaliveMs ?? KEEPALIVE_MS
39346
40024
  };
39347
40025
  return new Elysia().post("/v1/messages", ({ request: request2 }) => handle(dispatchDeps, rateLimiter, "anthropic", request2)).post("/v1/chat/completions", ({ request: request2 }) => handle(dispatchDeps, rateLimiter, "openai", request2)).get("/v1/models", async ({ request: request2 }) => {
39348
40026
  try {
39349
40027
  const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
39350
- const models = await deps.store.config.listModels();
40028
+ const snapshot = await dispatchDeps.snapshots.get(deps.now());
40029
+ const models = [...snapshot.models.values()];
39351
40030
  const visibleModels = key.modelAllowlist === null ? models : models.filter((model) => key.modelAllowlist?.includes(model.id));
39352
- return Response.json({
39353
- object: "list",
39354
- data: visibleModels.map((m) => ({
39355
- id: m.id,
39356
- object: "model",
39357
- created: 0,
39358
- owned_by: "omnigateway"
39359
- }))
39360
- });
40031
+ return Response.json(modelListBody(visibleModels, snapshot.credentials));
39361
40032
  } catch (error51) {
39362
40033
  const gatewayError = asGatewayError(error51);
40034
+ logger2.error("model listing failed", {
40035
+ status: HTTP_STATUS[gatewayError.code],
40036
+ code: gatewayError.code,
40037
+ reason: gatewayError.message
40038
+ });
39363
40039
  return errorResponse("anthropic", gatewayError.code, gatewayError.message);
39364
40040
  }
39365
40041
  }).onError(({ error: error51 }) => {
39366
40042
  const gatewayError = asGatewayError(error51);
40043
+ logger2.error("unhandled proxy route error", {
40044
+ status: HTTP_STATUS[gatewayError.code],
40045
+ code: gatewayError.code,
40046
+ reason: gatewayError.message
40047
+ });
39367
40048
  return errorResponse("anthropic", gatewayError.code, gatewayError.message);
39368
40049
  });
39369
40050
  }
@@ -39372,18 +40053,20 @@ function proxyRoutes(deps) {
39372
40053
  var ADMIN_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
39373
40054
  function createApp(deps) {
39374
40055
  const now = deps.now ?? (() => Date.now());
40056
+ const logger2 = deps.logger ?? noopLogger;
39375
40057
  const rand = deps.rand ?? Math.random;
39376
- const http = deps.http ?? nodeHttpClient();
40058
+ const http = deps.http ?? nodeHttpClient({ logger: logger2, now });
39377
40059
  const adapters = deps.adapters ?? ADAPTERS;
39378
40060
  const requestId = deps.requestId ?? (() => `req_${crypto.randomUUID()}`);
39379
40061
  const rateLimiter = new ApiKeyRateLimiter(now);
39380
- const snapshots = createRoutingSnapshotCache(deps.store);
40062
+ const snapshots = createRoutingSnapshotCache(deps.store, logger2);
39381
40063
  const admin = createAdminAuth(deps.store, { now, sessionTtlMs: ADMIN_SESSION_TTL_MS });
39382
40064
  const refresh = deps.refresh ?? createRefresher({
39383
40065
  store: deps.store,
39384
40066
  providers: OAUTH_PROVIDERS,
39385
40067
  http,
39386
- now
40068
+ now,
40069
+ logger: logger2
39387
40070
  });
39388
40071
  const staticDir = deps.staticDir ? resolve(deps.staticDir) : undefined;
39389
40072
  let staticRoot;
@@ -39401,13 +40084,22 @@ function createApp(deps) {
39401
40084
  rand,
39402
40085
  refresh,
39403
40086
  requestId,
39404
- rateLimiter
39405
- })).use(adminRoutes({ store: deps.store, admin, now, sessionTtlMs: ADMIN_SESSION_TTL_MS })).use(connectRoutes({
40087
+ rateLimiter,
40088
+ logger: logger2
40089
+ })).use(adminRoutes({
40090
+ store: deps.store,
40091
+ admin,
40092
+ now,
40093
+ sessionTtlMs: ADMIN_SESSION_TTL_MS,
40094
+ logger: logger2,
40095
+ ...deps.console === undefined ? {} : { console: deps.console }
40096
+ })).use(connectRoutes({
39406
40097
  store: deps.store,
39407
40098
  admin,
39408
40099
  providers: OAUTH_PROVIDERS,
39409
40100
  http,
39410
- now
40101
+ now,
40102
+ logger: logger2
39411
40103
  })).get("/*", ({ path, request: request2, set: set2 }) => {
39412
40104
  const rawPath = new URL(request2.url).pathname;
39413
40105
  const error51 = () => {
@@ -39465,13 +40157,15 @@ var DAY_MS = 24 * 60 * 60 * 1000;
39465
40157
  var ROLLUP_RETENTION_DAYS = 400;
39466
40158
  async function pruneLogs(store, now) {
39467
40159
  const settings = await store.config.getSettings();
39468
- await store.usage.prune(now - settings.logRetentionDays * DAY_MS);
39469
- await store.usage.pruneDaily(now - ROLLUP_RETENTION_DAYS * DAY_MS);
40160
+ const raw = await store.usage.prune(now - settings.logRetentionDays * DAY_MS);
40161
+ const daily = await store.usage.pruneDaily(now - ROLLUP_RETENTION_DAYS * DAY_MS);
40162
+ return { raw, daily };
39470
40163
  }
39471
40164
  function startMaintenance(deps) {
40165
+ const logger2 = deps.logger ?? noopLogger;
39472
40166
  const timer = setInterval(() => {
39473
- pruneLogs(deps.store, deps.now()).catch((error51) => {
39474
- console.error("log pruning failed", {
40167
+ pruneLogs(deps.store, deps.now()).then(({ raw, daily }) => logger2.debug("request logs pruned", { rawCount: raw, dailyCount: daily })).catch((error51) => {
40168
+ logger2.error("log pruning failed", {
39475
40169
  reason: error51 instanceof Error ? error51.message : "unknown"
39476
40170
  });
39477
40171
  });
@@ -39486,6 +40180,7 @@ function due(credentials, now) {
39486
40180
  return credentials.filter((c) => c.enabled && c.authType === "oauth" && c.expiresAt !== null && c.expiresAt - SCHEDULER_REFRESH_LEAD_MS <= now);
39487
40181
  }
39488
40182
  async function sweep(deps) {
40183
+ const logger2 = deps.logger ?? noopLogger;
39489
40184
  const credentials = await deps.store.credentials.list();
39490
40185
  let refreshed = 0;
39491
40186
  for (const credential of due(credentials, deps.now())) {
@@ -39504,23 +40199,25 @@ async function sweep(deps) {
39504
40199
  refreshed += 1;
39505
40200
  } catch (error51) {
39506
40201
  const code = error51 instanceof GatewayError ? error51.code : "INTERNAL";
39507
- console.warn("scheduled token refresh failed", {
39508
- credentialId: credential.id,
40202
+ logger2.warn("scheduled token refresh failed", {
39509
40203
  provider: credential.provider,
39510
- code
40204
+ credentialId: credential.id,
40205
+ code,
40206
+ reason: error51 instanceof Error ? error51.message : "unknown"
39511
40207
  });
39512
40208
  }
39513
40209
  }
39514
40210
  return refreshed;
39515
40211
  }
39516
40212
  function startRefreshScheduler(deps) {
40213
+ const logger2 = deps.logger ?? noopLogger;
39517
40214
  let running = false;
39518
40215
  const timer = setInterval(() => {
39519
40216
  if (running)
39520
40217
  return;
39521
40218
  running = true;
39522
40219
  sweep(deps).catch((error51) => {
39523
- console.error("token refresh sweep failed", {
40220
+ logger2.error("token refresh sweep failed", {
39524
40221
  reason: error51 instanceof Error ? error51.message : "unknown"
39525
40222
  });
39526
40223
  }).finally(() => {
@@ -39533,6 +40230,7 @@ function startRefreshScheduler(deps) {
39533
40230
 
39534
40231
  // apps/gateway/src/quota/poller.ts
39535
40232
  async function startQuotaPoller(deps) {
40233
+ const logger2 = deps.logger ?? noopLogger;
39536
40234
  const { quotaPollIntervalMs } = await deps.store.config.getSettings();
39537
40235
  if (quotaPollIntervalMs <= 0)
39538
40236
  return () => {};
@@ -39542,7 +40240,7 @@ async function startQuotaPoller(deps) {
39542
40240
  return;
39543
40241
  running = true;
39544
40242
  poll(deps).catch((error51) => {
39545
- console.error("quota poll failed", {
40243
+ logger2.error("quota poll failed", {
39546
40244
  reason: error51 instanceof Error ? error51.message : "unknown"
39547
40245
  });
39548
40246
  }).finally(() => {
@@ -39556,62 +40254,128 @@ async function startQuotaPoller(deps) {
39556
40254
  }
39557
40255
 
39558
40256
  // apps/gateway/src/index.ts
39559
- var config2 = loadConfig(process.env);
39560
- function dashboardDir() {
39561
- if (config2.staticDir !== null)
39562
- return config2.staticDir;
39563
- const candidates = [
39564
- resolve2(import.meta.dir, "./public"),
39565
- resolve2(import.meta.dir, "../../dashboard/dist")
39566
- ];
39567
- return candidates.find((path) => existsSync(path)) ?? candidates[1];
39568
- }
39569
- var encryptionKey = await deriveKey(config2.encryptionKey);
39570
- var store = await createStore({
39571
- path: config2.databasePath,
39572
- encryptionKey
39573
- });
39574
- var swept = await store.usage.sweepPending();
39575
- if (swept > 0)
39576
- console.log(`retired ${swept} request(s) interrupted by the last shutdown`);
39577
- var now = () => Date.now();
39578
- var http = nodeHttpClient();
39579
- var refresh = createRefresher({ store, providers: OAUTH_PROVIDERS, http, now });
39580
- var app = createApp({
39581
- store,
39582
- baseUrl: config2.baseUrl,
39583
- http,
39584
- now,
39585
- refresh,
39586
- staticDir: dashboardDir()
39587
- });
39588
- var stopMaintenance = startMaintenance({ store, now });
39589
- var stopRefreshScheduler = startRefreshScheduler({ store, refresh, now });
39590
- var stopQuotaPoller = await startQuotaPoller({
39591
- store,
39592
- providers: OAUTH_PROVIDERS,
39593
- http,
39594
- refresh,
39595
- now
39596
- });
39597
- app.listen({ port: config2.port, hostname: config2.host, idleTimeout: 255 });
39598
- console.log(`omnigateway listening on http://${config2.host}:${config2.port}`);
39599
- var shuttingDown = false;
39600
- function exitAfterClosingStore(code) {
39601
- try {
39602
- store.close();
39603
- } finally {
39604
- process.exit(code);
40257
+ function stdoutLogger(level) {
40258
+ return createLogger({
40259
+ level,
40260
+ color: Boolean(process.stdout.isTTY),
40261
+ write: (line) => {
40262
+ process.stdout.write(`${line}
40263
+ `);
40264
+ }
40265
+ });
40266
+ }
40267
+ var logger2 = stdoutLogger("info");
40268
+ function consoleSource(logFile) {
40269
+ const source = resolveConsoleSource({
40270
+ logFile,
40271
+ unitInstalled: process.env.JOURNAL_STREAM !== undefined,
40272
+ scope: process.env.MANAGERPID === undefined ? "system" : "user"
40273
+ });
40274
+ return {
40275
+ source,
40276
+ deps: {
40277
+ readFile: (path, lines) => tailFile(path, lines),
40278
+ run: async (argv) => {
40279
+ const [cmd, ...args] = argv;
40280
+ if (cmd === undefined)
40281
+ return { code: 1, stdout: "", stderr: "empty argv" };
40282
+ const proc = Bun.spawn([cmd, ...args], { stdout: "pipe", stderr: "pipe" });
40283
+ const [stdout, stderr] = await Promise.all([
40284
+ new Response(proc.stdout).text(),
40285
+ new Response(proc.stderr).text()
40286
+ ]);
40287
+ return { code: await proc.exited, stdout, stderr };
40288
+ }
40289
+ }
40290
+ };
40291
+ }
40292
+ async function main() {
40293
+ const config2 = loadConfig(process.env);
40294
+ logger2 = stdoutLogger(config2.logLevel);
40295
+ if (config2.logLevelFallbackFrom !== null) {
40296
+ logger2.info("invalid log level; using info", { reason: config2.logLevelFallbackFrom });
40297
+ }
40298
+ logger2.info("omnigateway booting", { host: config2.host, port: config2.port });
40299
+ function dashboardDir() {
40300
+ if (config2.staticDir !== null)
40301
+ return config2.staticDir;
40302
+ const candidates = [
40303
+ resolve2(import.meta.dir, "./public"),
40304
+ resolve2(import.meta.dir, "../../dashboard/dist")
40305
+ ];
40306
+ return candidates.find((path) => existsSync(path)) ?? candidates[1];
40307
+ }
40308
+ const encryptionKey = await deriveKey(config2.encryptionKey);
40309
+ const store = await createStore({
40310
+ path: config2.databasePath,
40311
+ encryptionKey,
40312
+ logger: logger2
40313
+ });
40314
+ const swept = await store.usage.sweepPending();
40315
+ if (swept > 0)
40316
+ logger2.info("retired interrupted requests", { count: swept });
40317
+ const console2 = consoleSource(config2.logFile);
40318
+ logger2.info("console log source resolved", { reason: console2.source.kind });
40319
+ const now = () => Date.now();
40320
+ const http = nodeHttpClient({ logger: logger2, now });
40321
+ const refresh = createRefresher({ store, providers: OAUTH_PROVIDERS, http, now, logger: logger2 });
40322
+ const staticDir = dashboardDir();
40323
+ logger2.info(existsSync(staticDir) ? "dashboard directory resolved" : "dashboard directory absent", {
40324
+ path: staticDir
40325
+ });
40326
+ const app = createApp({
40327
+ store,
40328
+ baseUrl: config2.baseUrl,
40329
+ http,
40330
+ now,
40331
+ refresh,
40332
+ staticDir,
40333
+ logger: logger2,
40334
+ console: console2
40335
+ });
40336
+ const stopMaintenance = startMaintenance({ store, now, logger: logger2 });
40337
+ const stopRefreshScheduler = startRefreshScheduler({ store, refresh, now, logger: logger2 });
40338
+ const stopQuotaPoller = await startQuotaPoller({
40339
+ store,
40340
+ providers: OAUTH_PROVIDERS,
40341
+ http,
40342
+ refresh,
40343
+ now,
40344
+ logger: logger2
40345
+ });
40346
+ app.listen({ port: config2.port, hostname: config2.host, idleTimeout: 255 });
40347
+ logger2.info("omnigateway listening", { host: config2.host, port: config2.port });
40348
+ let shuttingDown = false;
40349
+ function exitAfterClosingStore(code) {
40350
+ try {
40351
+ store.close();
40352
+ } finally {
40353
+ process.exit(code);
40354
+ }
40355
+ }
40356
+ for (const signal of ["SIGINT", "SIGTERM"]) {
40357
+ process.on(signal, () => {
40358
+ if (shuttingDown)
40359
+ exitAfterClosingStore(1);
40360
+ logger2.info("shutdown requested", { reason: signal });
40361
+ shuttingDown = true;
40362
+ stopMaintenance();
40363
+ stopRefreshScheduler();
40364
+ stopQuotaPoller();
40365
+ app.stop().then(() => exitAfterClosingStore(0), (error51) => {
40366
+ logger2.error("shutdown failed", {
40367
+ reason: error51 instanceof Error ? error51.message : "unknown"
40368
+ });
40369
+ return exitAfterClosingStore(1);
40370
+ });
40371
+ });
39605
40372
  }
39606
40373
  }
39607
- for (const signal of ["SIGINT", "SIGTERM"]) {
39608
- process.on(signal, () => {
39609
- if (shuttingDown)
39610
- exitAfterClosingStore(1);
39611
- shuttingDown = true;
39612
- stopMaintenance();
39613
- stopRefreshScheduler();
39614
- stopQuotaPoller();
39615
- app.stop().then(() => exitAfterClosingStore(0), () => exitAfterClosingStore(1));
40374
+ try {
40375
+ await main();
40376
+ } catch (error51) {
40377
+ logger2.error("gateway boot failed", {
40378
+ reason: error51 instanceof Error ? error51.message : "unknown"
39616
40379
  });
40380
+ process.exit(1);
39617
40381
  }