omnigateway 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/gateway.js CHANGED
@@ -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,117 @@ 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
+ function createLogger(opts) {
5418
+ const threshold = LEVELS[opts.level];
5419
+ const now = opts.now ?? (() => Date.now());
5420
+ const color = opts.color ?? false;
5421
+ const emit = (level, msg, fields) => {
5422
+ if (LEVELS[level] < threshold)
5423
+ return;
5424
+ try {
5425
+ opts.write(formatLine(level, now(), msg, fields, color));
5426
+ } catch {}
5427
+ };
5428
+ return {
5429
+ debug: (msg, fields) => emit("debug", msg, fields),
5430
+ info: (msg, fields) => emit("info", msg, fields),
5431
+ warn: (msg, fields) => emit("warn", msg, fields),
5432
+ error: (msg, fields) => emit("error", msg, fields),
5433
+ enabled: (level) => LEVELS[level] >= threshold
5434
+ };
5435
+ }
5436
+ var noopLogger = {
5437
+ debug: () => {},
5438
+ info: () => {},
5439
+ warn: () => {},
5440
+ error: () => {},
5441
+ enabled: () => false
5442
+ };
5443
+ function parseLogLevel(value) {
5444
+ const normalized = value?.trim().toLowerCase();
5445
+ if (normalized === undefined || normalized.length === 0)
5446
+ return null;
5447
+ return Object.hasOwn(LEVELS, normalized) ? normalized : null;
5448
+ }
5449
+ // packages/ir/src/request.ts
5450
+ function cacheControlOf(block) {
5451
+ return block.type === "thinking" ? undefined : block.cacheControl;
5452
+ }
5383
5453
  // packages/ir/src/stream.ts
5454
+ function usageFromPromptTotal(promptTokens, outputTokens, cacheReadTokens, cacheWriteTokens = 0) {
5455
+ return {
5456
+ inputTokens: Math.max(0, promptTokens - cacheReadTokens - cacheWriteTokens),
5457
+ outputTokens,
5458
+ cacheReadTokens,
5459
+ cacheWriteTokens
5460
+ };
5461
+ }
5462
+ function promptTokens(usage) {
5463
+ return usage.inputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
5464
+ }
5384
5465
  function collect(events) {
5385
5466
  let id = "";
5386
5467
  let model = "";
@@ -5473,6 +5554,39 @@ function validateRequest(req) {
5473
5554
  }
5474
5555
  return { ...req, messages: cleaned };
5475
5556
  }
5557
+ // packages/control/src/config.ts
5558
+ var MIN_KEY_LENGTH = 16;
5559
+ var DECIMAL_INTEGER = /^\d+$/;
5560
+ function optionalText(value, fallback) {
5561
+ return value?.trim() || fallback;
5562
+ }
5563
+ function loadConfig(env) {
5564
+ const encryptionKey = env.OMNI_ENCRYPTION_KEY;
5565
+ if (typeof encryptionKey !== "string" || encryptionKey.length < MIN_KEY_LENGTH) {
5566
+ throw new Error(`OMNI_ENCRYPTION_KEY must be set to at least ${MIN_KEY_LENGTH} characters. ` + "Generate one with: openssl rand -base64 32");
5567
+ }
5568
+ const host = optionalText(env.OMNI_HOST, "127.0.0.1");
5569
+ const rawPort = env.OMNI_PORT ?? "9000";
5570
+ const port = Number(rawPort);
5571
+ if (!DECIMAL_INTEGER.test(rawPort) || !Number.isInteger(port) || port < 1 || port > 65535) {
5572
+ throw new Error(`OMNI_PORT must be an integer between 1 and 65535, got "${rawPort}"`);
5573
+ }
5574
+ const derivedBaseUrl = `http://${host}:${port}`;
5575
+ const baseUrl = optionalText(env.OMNI_BASE_URL, derivedBaseUrl).replace(/\/+$/, "") || derivedBaseUrl;
5576
+ const staticDir = env.OMNI_STATIC_DIR?.trim();
5577
+ const rawLogLevel = env.OMNI_LOG_LEVEL?.trim();
5578
+ const logLevel = parseLogLevel(rawLogLevel);
5579
+ return {
5580
+ logLevel: logLevel ?? "info",
5581
+ logLevelFallbackFrom: logLevel === null && rawLogLevel ? rawLogLevel : null,
5582
+ port,
5583
+ host,
5584
+ databasePath: optionalText(env.OMNI_DB_PATH, "./omnigateway.db"),
5585
+ encryptionKey,
5586
+ baseUrl,
5587
+ staticDir: staticDir === undefined || staticDir.length === 0 ? null : staticDir
5588
+ };
5589
+ }
5476
5590
  // packages/providers/src/body.ts
5477
5591
  var BODY_ORDER = {
5478
5592
  anthropic: [
@@ -5559,8 +5673,13 @@ function applyAnthropicSystem(system) {
5559
5673
  let rewritten = text;
5560
5674
  for (const [from, to] of REWRITES)
5561
5675
  rewritten = rewritten.replaceAll(from, to);
5562
- if (rewritten.trim().length > 0)
5563
- kept.push({ type: "text", text: rewritten });
5676
+ if (rewritten.trim().length > 0) {
5677
+ kept.push({
5678
+ type: "text",
5679
+ text: rewritten,
5680
+ ...block.cache_control === undefined ? {} : { cache_control: block.cache_control }
5681
+ });
5682
+ }
5564
5683
  }
5565
5684
  return [{ type: "text", text: billingBlock() }, { type: "text", text: AGENT_PREAMBLE }, ...kept];
5566
5685
  }
@@ -5864,6 +5983,8 @@ async function* decodeAnthropic(messages) {
5864
5983
  let inputTokens = 0;
5865
5984
  let cacheReadTokens = 0;
5866
5985
  let cacheWriteTokens = 0;
5986
+ let cacheWrite5mTokens;
5987
+ let cacheWrite1hTokens;
5867
5988
  let outputTokens = 0;
5868
5989
  let stopReason = "endTurn";
5869
5990
  let terminal = false;
@@ -5877,6 +5998,8 @@ async function* decodeAnthropic(messages) {
5877
5998
  inputTokens = m.usage?.input_tokens ?? 0;
5878
5999
  cacheReadTokens = m.usage?.cache_read_input_tokens ?? 0;
5879
6000
  cacheWriteTokens = m.usage?.cache_creation_input_tokens ?? 0;
6001
+ cacheWrite5mTokens = m.usage?.cache_creation?.ephemeral_5m_input_tokens;
6002
+ cacheWrite1hTokens = m.usage?.cache_creation?.ephemeral_1h_input_tokens;
5880
6003
  yield { type: "start", id: String(m.id ?? ""), model: String(m.model ?? "") };
5881
6004
  break;
5882
6005
  }
@@ -5936,7 +6059,14 @@ async function* decodeAnthropic(messages) {
5936
6059
  yield {
5937
6060
  type: "end",
5938
6061
  stopReason,
5939
- usage: { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens }
6062
+ usage: {
6063
+ inputTokens,
6064
+ outputTokens,
6065
+ cacheReadTokens,
6066
+ cacheWriteTokens,
6067
+ ...cacheWrite5mTokens === undefined ? {} : { cacheWrite5mTokens },
6068
+ ...cacheWrite1hTokens === undefined ? {} : { cacheWrite1hTokens }
6069
+ }
5940
6070
  };
5941
6071
  break;
5942
6072
  case "error": {
@@ -5966,25 +6096,33 @@ async function* decodeAnthropic(messages) {
5966
6096
 
5967
6097
  // packages/providers/src/anthropic/wire.ts
5968
6098
  var OAUTH_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude.";
6099
+ function wireCacheControl(c) {
6100
+ if (c === undefined)
6101
+ return {};
6102
+ return { cache_control: { type: c.type, ...c.ttl === undefined ? {} : { ttl: c.ttl } } };
6103
+ }
5969
6104
  function encodeBlock(b) {
6105
+ const cache = wireCacheControl(cacheControlOf(b));
5970
6106
  switch (b.type) {
5971
6107
  case "text":
5972
- return { type: "text", text: b.text };
6108
+ return { type: "text", text: b.text, ...cache };
5973
6109
  case "image":
5974
6110
  return {
5975
6111
  type: "image",
5976
- source: { type: "base64", media_type: b.mediaType, data: b.data }
6112
+ source: { type: "base64", media_type: b.mediaType, data: b.data },
6113
+ ...cache
5977
6114
  };
5978
6115
  case "thinking":
5979
6116
  return { type: "thinking", thinking: b.text, signature: b.signature };
5980
6117
  case "toolUse":
5981
- return { type: "tool_use", id: b.id, name: b.name, input: b.input };
6118
+ return { type: "tool_use", id: b.id, name: b.name, input: b.input, ...cache };
5982
6119
  case "toolResult":
5983
6120
  return {
5984
6121
  type: "tool_result",
5985
6122
  tool_use_id: b.toolUseId,
5986
6123
  content: b.content,
5987
- is_error: b.isError
6124
+ is_error: b.isError,
6125
+ ...cache
5988
6126
  };
5989
6127
  }
5990
6128
  }
@@ -6006,14 +6144,25 @@ function encodeToolChoice(c) {
6006
6144
  }
6007
6145
  function toWire(req, model, opts) {
6008
6146
  const degradations = [];
6009
- let system = req.system?.flatMap((b) => b.type === "text" ? [{ type: "text", text: b.text }] : []);
6147
+ const note = (d) => {
6148
+ if (!degradations.includes(d))
6149
+ degradations.push(d);
6150
+ };
6151
+ let system = req.system?.flatMap((b) => b.type === "text" ? [{ type: "text", text: b.text, ...wireCacheControl(b.cacheControl) }] : []);
6010
6152
  if (opts.oauth && system?.[0]?.text !== OAUTH_IDENTITY) {
6011
6153
  system = [{ type: "text", text: OAUTH_IDENTITY }, ...system ?? []];
6012
- degradations.push("anthropic:oauth-system-prefix");
6154
+ note("anthropic:oauth-system-prefix");
6013
6155
  }
6014
6156
  const body = {
6015
6157
  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) }),
6158
+ messages: req.messages.map((m) => {
6159
+ if (m.role !== "system")
6160
+ return { role: m.role, content: m.content.map(encodeBlock) };
6161
+ if (m.content.some((b) => cacheControlOf(b) !== undefined)) {
6162
+ note("anthropic:system-turn-cache-control-dropped");
6163
+ }
6164
+ return { role: m.role, content: encodeSystemTurn(m.content) };
6165
+ }),
6017
6166
  max_tokens: req.maxTokens ?? 4096,
6018
6167
  stream: req.stream
6019
6168
  };
@@ -6027,7 +6176,8 @@ function toWire(req, model, opts) {
6027
6176
  body.tools = req.tools.map((t) => ({
6028
6177
  name: t.name,
6029
6178
  ...t.description === undefined ? {} : { description: t.description },
6030
- input_schema: t.inputSchema
6179
+ input_schema: t.inputSchema,
6180
+ ...wireCacheControl(t.cacheControl)
6031
6181
  }));
6032
6182
  }
6033
6183
  if (req.toolChoice !== undefined)
@@ -6067,12 +6217,13 @@ var anthropicAdapter = {
6067
6217
  const { body, degradations } = toWire(req.request, req.model, { oauth });
6068
6218
  const withSystem = {
6069
6219
  ...body,
6070
- system: applyAnthropicSystem(body.system ?? [])
6220
+ system: applyAnthropicSystem(body.system ?? []),
6221
+ stream: true
6071
6222
  };
6072
6223
  const protocol = [
6073
6224
  ["Content-Type", "application/json"],
6074
6225
  ["anthropic-version", API_VERSION],
6075
- ["Accept", req.request.stream ? "text/event-stream" : "application/json"]
6226
+ ["Accept", "text/event-stream"]
6076
6227
  ];
6077
6228
  const betas = new Set(req.request.betas ?? []);
6078
6229
  if (oauth) {
@@ -6091,6 +6242,7 @@ var anthropicAdapter = {
6091
6242
  const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
6092
6243
  const bodyString = signAnthropicBody(JSON.stringify(orderFields(withSystem, BODY_ORDER.anthropic)));
6093
6244
  const res = await req.http({
6245
+ provider: "anthropic",
6094
6246
  url: BASE_URL,
6095
6247
  method: "POST",
6096
6248
  headers,
@@ -6111,22 +6263,26 @@ var ANTHROPIC_MODELS = {
6111
6263
  {
6112
6264
  id: "claude-fable-5",
6113
6265
  label: "Claude Fable 5",
6114
- pricing: { input: 10, output: 50, cacheRead: 1 }
6266
+ pricing: { input: 10, output: 50, cacheRead: 1, cacheWrite5m: 12.5, cacheWrite1h: 20 },
6267
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
6115
6268
  },
6116
6269
  {
6117
6270
  id: "claude-opus-5",
6118
6271
  label: "Claude Opus 5",
6119
- pricing: { input: 5, output: 25, cacheRead: 0.5 }
6272
+ pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite5m: 6.25, cacheWrite1h: 10 },
6273
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
6120
6274
  },
6121
6275
  {
6122
6276
  id: "claude-sonnet-5",
6123
6277
  label: "Claude Sonnet 5",
6124
- pricing: { input: 3, output: 15, cacheRead: 0.3 }
6278
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 3.75, cacheWrite1h: 6 },
6279
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
6125
6280
  },
6126
6281
  {
6127
6282
  id: "claude-haiku-4-5",
6128
6283
  label: "Claude Haiku 4.5",
6129
- pricing: { input: 1, output: 5, cacheRead: 0.1 }
6284
+ pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
6285
+ limits: { contextWindow: 200000, maxOutputTokens: 64000 }
6130
6286
  }
6131
6287
  ]
6132
6288
  };
@@ -6138,22 +6294,26 @@ var KIMI_MODELS = {
6138
6294
  {
6139
6295
  id: "k3-256k",
6140
6296
  label: "Kimi K3 \u2014 256K",
6141
- pricing: { input: 3, output: 15, cacheRead: 0.3 }
6297
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
6298
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
6142
6299
  },
6143
6300
  {
6144
6301
  id: "k3",
6145
6302
  label: "Kimi K3 \u2014 up to 1M",
6146
- pricing: { input: 3, output: 15, cacheRead: 0.3 }
6303
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
6304
+ limits: { contextWindow: 1048576, maxOutputTokens: 131072 }
6147
6305
  },
6148
6306
  {
6149
6307
  id: "kimi-for-coding",
6150
6308
  label: "Kimi K2.7 Code",
6151
- pricing: { input: 0.95, output: 4, cacheRead: 0.19 }
6309
+ pricing: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
6310
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
6152
6311
  },
6153
6312
  {
6154
6313
  id: "kimi-for-coding-highspeed",
6155
6314
  label: "Kimi K2.7 Code \u2014 High Speed",
6156
- pricing: { input: 0.95, output: 8, cacheRead: 0.19 }
6315
+ pricing: { input: 0.95, output: 8, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
6316
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
6157
6317
  }
6158
6318
  ]
6159
6319
  };
@@ -6165,22 +6325,30 @@ var OPENAI_MODELS = {
6165
6325
  {
6166
6326
  id: "gpt-5.6",
6167
6327
  label: "GPT-5.6 \u2014 routes to Sol",
6168
- pricing: { input: 5, output: 30, cacheRead: 0.5 }
6328
+ pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
6329
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
6330
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
6169
6331
  },
6170
6332
  {
6171
6333
  id: "gpt-5.6-sol",
6172
6334
  label: "GPT-5.6 Sol \u2014 deepest reasoning",
6173
- pricing: { input: 5, output: 30, cacheRead: 0.5 }
6335
+ pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
6336
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
6337
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
6174
6338
  },
6175
6339
  {
6176
6340
  id: "gpt-5.6-terra",
6177
6341
  label: "GPT-5.6 Terra \u2014 balanced",
6178
- pricing: { input: 2, output: 12, cacheRead: 0.2 }
6342
+ pricing: { input: 2, output: 12, cacheRead: 0.2, cacheWrite5m: 0, cacheWrite1h: 0 },
6343
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
6344
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
6179
6345
  },
6180
6346
  {
6181
6347
  id: "gpt-5.6-luna",
6182
6348
  label: "GPT-5.6 Luna \u2014 fastest",
6183
- pricing: { input: 0.2, output: 1.2, cacheRead: 0.02 }
6349
+ pricing: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite5m: 0, cacheWrite1h: 0 },
6350
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
6351
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
6184
6352
  }
6185
6353
  ]
6186
6354
  };
@@ -6192,15 +6360,38 @@ var PROVIDER_MODEL_CATALOG = {
6192
6360
  kimi: KIMI_MODELS
6193
6361
  };
6194
6362
  function catalogPricing(provider, model) {
6195
- return PROVIDER_MODEL_CATALOG[provider].models.find((entry) => entry.id === model)?.pricing ?? null;
6363
+ return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
6364
+ }
6365
+ function catalogLimits(provider, model, auth = "apiKey") {
6366
+ const entry = PROVIDER_MODEL_CATALOG[provider]?.models.find((choice) => choice.id === model);
6367
+ if (entry === undefined)
6368
+ return null;
6369
+ return (auth === "oauth" ? entry.oauthLimits : undefined) ?? entry.limits;
6196
6370
  }
6197
6371
  // packages/providers/src/http-client.ts
6198
6372
  import { request as httpRequest } from "http";
6199
6373
  import { request as httpsRequest } from "https";
6200
6374
  import { Readable } from "stream";
6201
- function nodeHttpClient() {
6375
+ function nodeHttpClient(options = {}) {
6376
+ const logger2 = options.logger ?? noopLogger;
6377
+ const now = options.now ?? (() => Date.now());
6202
6378
  return (req) => new Promise((resolve, reject) => {
6203
6379
  const url = new URL(req.url);
6380
+ const startedAt = now();
6381
+ let traced = false;
6382
+ const trace = (status, failed = false) => {
6383
+ if (traced || !logger2.enabled("debug"))
6384
+ return;
6385
+ traced = true;
6386
+ logger2.debug("upstream http", {
6387
+ provider: req.provider,
6388
+ status,
6389
+ host: url.host,
6390
+ path: url.pathname,
6391
+ durationMs: now() - startedAt,
6392
+ reason: failed ? "transport error" : undefined
6393
+ });
6394
+ };
6204
6395
  const send = url.protocol === "https:" ? httpsRequest : httpRequest;
6205
6396
  const bodyBytes = Buffer.from(req.body, "utf8");
6206
6397
  const headers = {};
@@ -6228,6 +6419,7 @@ function nodeHttpClient() {
6228
6419
  else if (typeof v === "string")
6229
6420
  responseHeaders.set(k, v);
6230
6421
  }
6422
+ trace(incoming.statusCode);
6231
6423
  resolve({
6232
6424
  status: incoming.statusCode ?? 0,
6233
6425
  headers: responseHeaders,
@@ -6243,17 +6435,17 @@ function nodeHttpClient() {
6243
6435
  });
6244
6436
  });
6245
6437
  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
6438
  outgoing.on("error", (err) => {
6253
6439
  req.signal.removeEventListener("abort", onAbort);
6440
+ trace(undefined, true);
6254
6441
  reject(err);
6255
6442
  });
6256
6443
  outgoing.on("close", () => req.signal.removeEventListener("abort", onAbort));
6444
+ if (req.signal.aborted) {
6445
+ outgoing.destroy(new Error("aborted"));
6446
+ return;
6447
+ }
6448
+ req.signal.addEventListener("abort", onAbort, { once: true });
6257
6449
  if (bodyBytes.byteLength > 0)
6258
6450
  outgoing.write(bodyBytes);
6259
6451
  outgoing.end();
@@ -6322,12 +6514,8 @@ async function* decodeChat(messages) {
6322
6514
  yield { type: "start", id: String(d.id ?? ""), model: String(d.model ?? "") };
6323
6515
  }
6324
6516
  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
- };
6517
+ const details = d.usage.prompt_tokens_details;
6518
+ 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
6519
  }
6332
6520
  const choice = d.choices?.[0];
6333
6521
  if (!choice)
@@ -6453,7 +6641,12 @@ function toChatWire(req, model) {
6453
6641
  `) });
6454
6642
  }
6455
6643
  }
6456
- const body = { model, messages, stream: req.stream };
6644
+ const body = {
6645
+ model,
6646
+ messages,
6647
+ stream: req.stream,
6648
+ stream_options: { include_usage: true }
6649
+ };
6457
6650
  if (req.maxTokens !== undefined)
6458
6651
  body.max_tokens = req.maxTokens;
6459
6652
  if (req.temperature !== undefined)
@@ -6494,6 +6687,7 @@ var kimiAdapter = {
6494
6687
  const profile = PROFILES.kimi;
6495
6688
  const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
6496
6689
  const res = await req.http({
6690
+ provider: "kimi",
6497
6691
  url: BASE_URL2,
6498
6692
  method: "POST",
6499
6693
  headers,
@@ -6624,12 +6818,7 @@ async function* decodeResponses(messages) {
6624
6818
  yield {
6625
6819
  type: "end",
6626
6820
  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
- }
6821
+ 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
6822
  };
6634
6823
  break;
6635
6824
  }
@@ -6802,6 +6991,7 @@ var openaiAdapter = {
6802
6991
  const profile = PROFILES.openai;
6803
6992
  const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
6804
6993
  const res = await req.http({
6994
+ provider: "openai",
6805
6995
  url: oauth ? OAUTH_URL : API_URL,
6806
6996
  method: "POST",
6807
6997
  headers,
@@ -6822,13 +7012,14 @@ var ADAPTERS = {
6822
7012
  kimi: kimiAdapter
6823
7013
  };
6824
7014
  // packages/control/src/oauth/types.ts
6825
- async function postJson(deps, url, profile2, opts) {
7015
+ async function postJson(deps, provider, url, profile2, opts) {
6826
7016
  const headers = orderHeaders(mergeHeaders(profile2.headers, [
6827
7017
  ["Content-Type", opts.contentType],
6828
7018
  ["Accept", "application/json"],
6829
7019
  ...opts.extraHeaders ?? []
6830
7020
  ]), profile2.order);
6831
7021
  const res = await deps.http({
7022
+ provider,
6832
7023
  url,
6833
7024
  method: "POST",
6834
7025
  headers,
@@ -6842,13 +7033,14 @@ async function postJson(deps, url, profile2, opts) {
6842
7033
  } catch {}
6843
7034
  return { status: res.status, parsed };
6844
7035
  }
6845
- async function getJson(deps, url, profile2, opts) {
7036
+ async function getJson(deps, provider, url, profile2, opts) {
6846
7037
  const headers = orderHeaders(mergeHeaders(profile2.headers, [
6847
7038
  ["Authorization", `Bearer ${opts.accessToken}`],
6848
7039
  ["Accept", "application/json"],
6849
7040
  ...opts.extraHeaders ?? []
6850
7041
  ]), profile2.order);
6851
7042
  const res = await deps.http({
7043
+ provider,
6852
7044
  url,
6853
7045
  method: "GET",
6854
7046
  headers,
@@ -7042,7 +7234,7 @@ function deviceCodeFrom(value) {
7042
7234
  };
7043
7235
  }
7044
7236
  async function post(url, body2, device, deps) {
7045
- const { status, parsed } = await postJson(deps, url, PROFILES.kimi, {
7237
+ const { status, parsed } = await postJson(deps, "kimi", url, PROFILES.kimi, {
7046
7238
  contentType: "application/x-www-form-urlencoded",
7047
7239
  body: new URLSearchParams({ ...body2, client_id: CLIENT_ID }).toString(),
7048
7240
  extraHeaders: kimiDeviceHeaders(device)
@@ -7145,7 +7337,7 @@ var kimiOAuth = {
7145
7337
  if (secrets.accessToken === null)
7146
7338
  return null;
7147
7339
  const device = deviceFrom(providerData);
7148
- const { status, parsed } = await getJson(deps, USAGE_URL, PROFILES.kimi, {
7340
+ const { status, parsed } = await getJson(deps, "kimi", USAGE_URL, PROFILES.kimi, {
7149
7341
  accessToken: secrets.accessToken,
7150
7342
  extraHeaders: kimiDeviceHeaders(device)
7151
7343
  });
@@ -7214,6 +7406,7 @@ function deviceIdFrom(start) {
7214
7406
  return deviceId;
7215
7407
  }
7216
7408
  function createConnectFlows(deps) {
7409
+ const logger2 = deps.logger ?? noopLogger;
7217
7410
  const flows = createPendingFlows({ now: deps.now, ttlMs: FLOW_TTL_MS });
7218
7411
  const pollsInFlight = new Map;
7219
7412
  const callbackUri = (provider) => provider === "openai" ? "http://localhost:1455/auth/callback" : "";
@@ -7258,6 +7451,10 @@ function createConnectFlows(deps) {
7258
7451
  disabledAt: null,
7259
7452
  ...result.secrets
7260
7453
  });
7454
+ logger2.info("oauth connect completed", {
7455
+ provider: flow.provider,
7456
+ credentialId: id
7457
+ });
7261
7458
  return { id };
7262
7459
  }
7263
7460
  async function pollOutcome(flowId, poll) {
@@ -21633,28 +21830,18 @@ var modelSchema = exports_external.object({
21633
21830
  costPerMTok: exports_external.object({
21634
21831
  input: exports_external.number().min(0),
21635
21832
  output: exports_external.number().min(0),
21636
- cacheRead: exports_external.number().min(0)
21833
+ cacheRead: exports_external.number().min(0).optional(),
21834
+ cacheWrite5m: exports_external.number().min(0).optional(),
21835
+ cacheWrite1h: exports_external.number().min(0).optional()
21637
21836
  }),
21837
+ contextWindow: exports_external.number().int().positive().optional(),
21838
+ maxOutputTokens: exports_external.number().int().positive().optional(),
21638
21839
  capabilities: exports_external.object({
21639
21840
  tools: exports_external.boolean(),
21640
21841
  images: exports_external.boolean(),
21641
21842
  reasoning: exports_external.boolean()
21642
21843
  })
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)
21651
- }),
21652
- capabilities: exports_external.object({
21653
- tools: exports_external.boolean(),
21654
- images: exports_external.boolean(),
21655
- reasoning: exports_external.boolean()
21656
- })
21657
- }))).min(1, "a virtual model needs at least one target")
21844
+ })).min(1, "a virtual model needs at least one target")
21658
21845
  });
21659
21846
  var keyCreateSchema = exports_external.object({
21660
21847
  label: exports_external.string().min(1).default("api key"),
@@ -21748,6 +21935,12 @@ async function patchCredential(deps, id, input) {
21748
21935
  ...patch.tier === undefined ? {} : { tier: patch.tier },
21749
21936
  ...patch.weight === undefined ? {} : { weight: patch.weight }
21750
21937
  });
21938
+ if (patch.enabled !== undefined && patch.enabled !== existing.enabled) {
21939
+ (deps.logger ?? noopLogger).info(patch.enabled ? "credential enabled" : "credential disabled", {
21940
+ credentialId: id,
21941
+ provider: existing.provider
21942
+ });
21943
+ }
21751
21944
  }
21752
21945
  async function removeCredential(store, id) {
21753
21946
  await store.credentials.remove(id);
@@ -22009,12 +22202,20 @@ var PREFIX_PROVIDER = [
22009
22202
  ];
22010
22203
  function synthesize(provider, model) {
22011
22204
  const listed = catalogPricing(provider, model);
22205
+ const limits = catalogLimits(provider, model);
22012
22206
  const target = {
22013
22207
  provider,
22014
22208
  model,
22015
22209
  tier: 1,
22016
22210
  weight: 1,
22017
- costPerMTok: listed === null ? { input: 0, output: 0 } : { input: listed.input, output: listed.output, cacheRead: listed.cacheRead },
22211
+ costPerMTok: listed === null ? { input: 0, output: 0 } : {
22212
+ input: listed.input,
22213
+ output: listed.output,
22214
+ cacheRead: listed.cacheRead,
22215
+ cacheWrite5m: listed.cacheWrite5m,
22216
+ cacheWrite1h: listed.cacheWrite1h
22217
+ },
22218
+ ...limits === null ? {} : { contextWindow: limits.contextWindow, maxOutputTokens: limits.maxOutputTokens },
22018
22219
  capabilities: PROVIDER_CAPABILITIES[provider]
22019
22220
  };
22020
22221
  return { id: `${provider}/${model}`, targets: [target], strategy: "score", isAlias: true };
@@ -23017,7 +23218,9 @@ function createUsageRepo(db) {
23017
23218
 
23018
23219
  // packages/store/src/sqlite/store.ts
23019
23220
  async function createStore(opts) {
23221
+ const logger2 = opts.logger ?? noopLogger;
23020
23222
  const db = openDb(opts.path);
23223
+ logger2.debug("store opened", { path: opts.path });
23021
23224
  const listeners = new Set;
23022
23225
  const emit = (change) => {
23023
23226
  for (const listener of listeners) {
@@ -23104,7 +23307,7 @@ var REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
23104
23307
  var SCOPES = "org:create_api_key user:profile user:inference";
23105
23308
  var USAGE_URL2 = "https://api.anthropic.com/api/oauth/usage";
23106
23309
  async function postToken(body2, deps) {
23107
- const { status, parsed } = await postJson(deps, TOKEN_URL2, PROFILES.anthropic, {
23310
+ const { status, parsed } = await postJson(deps, "anthropic", TOKEN_URL2, PROFILES.anthropic, {
23108
23311
  contentType: "application/json",
23109
23312
  body: JSON.stringify({ ...body2, client_id: CLIENT_ID2 })
23110
23313
  });
@@ -23182,7 +23385,7 @@ var anthropicOAuth = {
23182
23385
  async usage(secrets, deps) {
23183
23386
  if (secrets.accessToken === null)
23184
23387
  return null;
23185
- const { status, parsed } = await getJson(deps, USAGE_URL2, PROFILES.anthropic, {
23388
+ const { status, parsed } = await getJson(deps, "anthropic", USAGE_URL2, PROFILES.anthropic, {
23186
23389
  accessToken: secrets.accessToken,
23187
23390
  extraHeaders: [
23188
23391
  ["anthropic-beta", "oauth-2025-04-20"],
@@ -23240,7 +23443,7 @@ function parseTokenResponse(value) {
23240
23443
  };
23241
23444
  }
23242
23445
  async function postToken2(body2, deps) {
23243
- const { status, parsed } = await postJson(deps, TOKEN_URL3, PROFILES.openai, {
23446
+ const { status, parsed } = await postJson(deps, "openai", TOKEN_URL3, PROFILES.openai, {
23244
23447
  contentType: "application/x-www-form-urlencoded",
23245
23448
  body: new URLSearchParams({ ...body2, client_id: CLIENT_ID3 }).toString()
23246
23449
  });
@@ -23339,7 +23542,7 @@ var openaiOAuth = {
23339
23542
  if (secrets.accessToken === null)
23340
23543
  return null;
23341
23544
  const accountId = accountIdFromProviderData(providerData);
23342
- const { status, parsed } = await getJson(deps, USAGE_URL3, PROFILES.openai, {
23545
+ const { status, parsed } = await getJson(deps, "openai", USAGE_URL3, PROFILES.openai, {
23343
23546
  accessToken: secrets.accessToken,
23344
23547
  ...accountId === null ? {} : { extraHeaders: [["chatgpt-account-id", accountId]] }
23345
23548
  });
@@ -23360,6 +23563,7 @@ var DISPATCH_REFRESH_LEAD_MS = 120000;
23360
23563
  var SCHEDULER_REFRESH_LEAD_MS = 300000;
23361
23564
  // packages/control/src/oauth/refresh.ts
23362
23565
  function createRefresher(deps) {
23566
+ const logger2 = deps.logger ?? noopLogger;
23363
23567
  const inFlight = new Map;
23364
23568
  async function run(credential) {
23365
23569
  const secrets = await credential.openForRefresh();
@@ -23367,11 +23571,20 @@ function createRefresher(deps) {
23367
23571
  throw new GatewayError("AUTH", `credential ${credential.id} has no refresh token`);
23368
23572
  }
23369
23573
  const provider = deps.providers[credential.provider];
23574
+ logger2.debug("refreshing credential", {
23575
+ provider: credential.provider,
23576
+ credentialId: credential.id
23577
+ });
23370
23578
  let result;
23371
23579
  try {
23372
23580
  result = await provider.refresh(secrets.refreshToken, { http: deps.http, now: deps.now }, credential.providerData);
23373
23581
  } catch (error51) {
23374
23582
  if (error51 instanceof GatewayError && error51.code === "AUTH") {
23583
+ logger2.warn("credential disabled: refresh token rejected", {
23584
+ provider: credential.provider,
23585
+ credentialId: credential.id,
23586
+ code: "AUTH"
23587
+ });
23375
23588
  await deps.store.credentials.update(credential.id, {
23376
23589
  enabled: false,
23377
23590
  disabledReason: "tokenRejected",
@@ -23385,6 +23598,10 @@ function createRefresher(deps) {
23385
23598
  accountEmail: result.accountEmail ?? credential.accountEmail,
23386
23599
  providerData: { ...credential.providerData, ...result.providerData }
23387
23600
  });
23601
+ logger2.debug("credential refreshed", {
23602
+ provider: credential.provider,
23603
+ credentialId: credential.id
23604
+ });
23388
23605
  return result.secrets;
23389
23606
  }
23390
23607
  return function refresh(credential) {
@@ -23424,9 +23641,15 @@ async function probe(deps, credential) {
23424
23641
  observedAt
23425
23642
  }));
23426
23643
  await deps.store.credentials.saveQuota(rows);
23644
+ (deps.logger ?? noopLogger).debug("quota snapshot written", {
23645
+ provider: credential.provider,
23646
+ credentialId: credential.id,
23647
+ count: rows.length
23648
+ });
23427
23649
  return rows;
23428
23650
  }
23429
23651
  async function poll(deps) {
23652
+ const logger2 = deps.logger ?? noopLogger;
23430
23653
  const now = deps.now();
23431
23654
  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
23655
  let written = 0;
@@ -23440,12 +23663,15 @@ async function poll(deps) {
23440
23663
  if (await probe(deps, credential) !== null)
23441
23664
  written += 1;
23442
23665
  } catch (error51) {
23443
- if (error51 instanceof GatewayError && error51.code === "RATE_LIMIT") {
23666
+ const rateLimited = error51 instanceof GatewayError && error51.code === "RATE_LIMIT";
23667
+ if (rateLimited) {
23444
23668
  cooldowns.set(credential.id, deps.now() + RATE_LIMIT_COOLDOWN_MS);
23445
23669
  }
23446
- console.warn("quota probe failed", {
23447
- credentialId: credential.id,
23670
+ logger2.warn(rateLimited ? "quota probe rate limited" : "quota probe failed", {
23448
23671
  provider: credential.provider,
23672
+ credentialId: credential.id,
23673
+ code: error51 instanceof GatewayError ? error51.code : "INTERNAL",
23674
+ ...rateLimited ? { retryAfterMs: RATE_LIMIT_COOLDOWN_MS } : {},
23449
23675
  reason: error51 instanceof Error ? error51.message : "unknown"
23450
23676
  });
23451
23677
  }
@@ -37903,7 +38129,7 @@ class ApiKeyRateLimiter {
37903
38129
  }
37904
38130
 
37905
38131
  // apps/gateway/src/dispatch/snapshotCache.ts
37906
- function createRoutingSnapshotCache(store) {
38132
+ function createRoutingSnapshotCache(store, logger2 = noopLogger) {
37907
38133
  let snapshot = null;
37908
38134
  let stale = true;
37909
38135
  let version2 = store.routing.version();
@@ -37942,6 +38168,7 @@ function createRoutingSnapshotCache(store) {
37942
38168
  return inFlight;
37943
38169
  const buildGeneration = generation;
37944
38170
  const buildVersion = currentVersion;
38171
+ logger2.debug("routing snapshot cache miss");
37945
38172
  inFlight = buildSnapshot(store, now).then(async (next) => {
37946
38173
  const completedVersion = store.routing.version();
37947
38174
  if (generation !== buildGeneration || completedVersion !== buildVersion) {
@@ -37961,6 +38188,28 @@ function createRoutingSnapshotCache(store) {
37961
38188
  }
37962
38189
 
37963
38190
  // apps/gateway/src/ingress/schemas.ts
38191
+ var cacheControlSchema = exports_external.object({
38192
+ type: exports_external.literal("ephemeral"),
38193
+ ttl: exports_external.enum(["5m", "1h"]).optional()
38194
+ });
38195
+ function looseCacheControl(value) {
38196
+ const parsed = cacheControlSchema.safeParse(value);
38197
+ return parsed.success ? irCacheControl(parsed.data) : {};
38198
+ }
38199
+ function irCacheControl(c) {
38200
+ if (c === undefined)
38201
+ return {};
38202
+ return { cacheControl: { type: c.type, ...c.ttl === undefined ? {} : { ttl: c.ttl } } };
38203
+ }
38204
+ function applyMessageCacheControl(blocks, value) {
38205
+ const control = looseCacheControl(value);
38206
+ if (control.cacheControl === undefined)
38207
+ return;
38208
+ const last = blocks.at(-1);
38209
+ if (last === undefined || last.type === "thinking" || cacheControlOf(last) !== undefined)
38210
+ return;
38211
+ Object.assign(last, control);
38212
+ }
37964
38213
  function parseOrThrow2(schema, body2) {
37965
38214
  const result = schema.safeParse(body2);
37966
38215
  if (result.success)
@@ -38040,6 +38289,7 @@ function apiErrorHandler(input) {
38040
38289
 
38041
38290
  // apps/gateway/src/routes/admin.ts
38042
38291
  function adminRoutes(deps) {
38292
+ const logger2 = deps.logger ?? noopLogger;
38043
38293
  return new Elysia().onError(apiErrorHandler).get("/api/status", async ({ request: request2 }) => {
38044
38294
  const token = readCookie(request2, ADMIN_COOKIE);
38045
38295
  return {
@@ -38068,13 +38318,17 @@ function adminRoutes(deps) {
38068
38318
  const token = await deps.admin.login(body2.password);
38069
38319
  if (token === null)
38070
38320
  throw new GatewayError("INTERNAL", "could not create admin session");
38321
+ logger2.info("admin setup completed");
38071
38322
  set2.headers["set-cookie"] = sessionCookie(request2, token, Math.floor(deps.sessionTtlMs / 1000));
38072
38323
  return { ok: true };
38073
38324
  }).post("/api/login", async ({ request: request2, set: set2 }) => {
38074
38325
  const body2 = await readJsonRecord(request2);
38075
38326
  const token = typeof body2?.password === "string" ? await deps.admin.login(body2.password) : null;
38076
- if (token === null)
38327
+ if (token === null) {
38328
+ logger2.info("admin login failed", { reason: "invalid credentials" });
38077
38329
  throw new GatewayError("AUTH", "invalid password");
38330
+ }
38331
+ logger2.info("admin login succeeded");
38078
38332
  set2.headers["set-cookie"] = sessionCookie(request2, token, Math.floor(deps.sessionTtlMs / 1000));
38079
38333
  return { ok: true };
38080
38334
  }).post("/api/logout", async ({ request: request2, set: set2 }) => {
@@ -38240,41 +38494,47 @@ function newPendingRequestLog(input) {
38240
38494
  status: 0
38241
38495
  };
38242
38496
  }
38243
- function report(what, requestId, error51) {
38244
- console.error(what, {
38497
+ function report(logger2, what, requestId, error51) {
38498
+ logger2.warn(what, {
38245
38499
  requestId,
38246
38500
  reason: error51 instanceof Error ? error51.message : "unknown"
38247
38501
  });
38248
38502
  }
38249
- async function beginLog(store, log, keyId) {
38503
+ async function beginLog(store, log, keyId, logger2 = noopLogger) {
38250
38504
  try {
38251
38505
  await store.usage.begin({ ...log, state: "pending", apiKeyId: keyId });
38252
38506
  } catch (error51) {
38253
- report("failed to record request start", log.id, error51);
38507
+ report(logger2, "failed to record request start", log.id, error51);
38254
38508
  }
38255
38509
  }
38256
- async function routeLog(store, requestId, target) {
38510
+ async function routeLog(store, requestId, target, logger2 = noopLogger) {
38257
38511
  try {
38258
38512
  await store.usage.route(requestId, target);
38259
38513
  } catch (error51) {
38260
- report("failed to record request route", requestId, error51);
38514
+ report(logger2, "failed to record request route", requestId, error51);
38261
38515
  }
38262
38516
  }
38263
- async function finishLog(store, log, keyId) {
38517
+ async function finishLog(store, log, keyId, logger2 = noopLogger) {
38264
38518
  try {
38265
38519
  await store.usage.append({ ...log, apiKeyId: keyId });
38266
38520
  } catch (error51) {
38267
- report("failed to persist request log", log.id, error51);
38521
+ report(logger2, "failed to persist request log", log.id, error51);
38268
38522
  }
38269
38523
  }
38270
38524
 
38271
38525
  // apps/gateway/src/dispatch/attempt.ts
38272
38526
  async function attempt(opts) {
38273
38527
  const { candidate, adapter, http, now, signal, refresh, refreshLeadMs } = opts;
38528
+ const logger2 = opts.logger ?? noopLogger;
38274
38529
  const credential = candidate.credential;
38275
38530
  let secrets = opts.secrets ?? await credential.openForInference();
38276
38531
  const stale = opts.secrets === undefined && credential.authType === "oauth" && credential.expiresAt !== null && credential.expiresAt - refreshLeadMs <= now;
38277
38532
  if (stale) {
38533
+ logger2.debug("preemptive credential refresh", {
38534
+ requestId: opts.requestId,
38535
+ provider: credential.provider,
38536
+ credentialId: credential.id
38537
+ });
38278
38538
  if (!credential.hasRefreshToken) {
38279
38539
  throw new GatewayError("AUTH", "credential expired with no refresh token", {
38280
38540
  provider: credential.provider
@@ -38320,8 +38580,35 @@ function classify(error51) {
38320
38580
  return { code: "INTERNAL" };
38321
38581
  }
38322
38582
 
38583
+ // apps/gateway/src/dispatch/price.ts
38584
+ var WRITE_OVER_INPUT = {
38585
+ anthropic: { fiveMinute: 1.25, oneHour: 2 },
38586
+ openai: { fiveMinute: 0, oneHour: 0 },
38587
+ kimi: { fiveMinute: 0, oneHour: 0 }
38588
+ };
38589
+ var READ_OVER_INPUT = 0.1;
38590
+ function splitWrites(usage) {
38591
+ const { cacheWrite5mTokens: five, cacheWrite1hTokens: hour, cacheWriteTokens: total } = usage;
38592
+ if (five !== undefined && hour !== undefined)
38593
+ return { fiveMinute: five, oneHour: hour };
38594
+ if (five !== undefined)
38595
+ return { fiveMinute: five, oneHour: Math.max(0, total - five) };
38596
+ if (hour !== undefined)
38597
+ return { fiveMinute: Math.max(0, total - hour), oneHour: hour };
38598
+ return { fiveMinute: total, oneHour: 0 };
38599
+ }
38600
+ function priceOf(prices, usage, provider) {
38601
+ const fallback = WRITE_OVER_INPUT[provider];
38602
+ const readRate = prices.cacheRead ?? prices.input * READ_OVER_INPUT;
38603
+ const write5mRate = prices.cacheWrite5m ?? prices.input * fallback.fiveMinute;
38604
+ const write1hRate = prices.cacheWrite1h ?? prices.input * fallback.oneHour;
38605
+ const writes = splitWrites(usage);
38606
+ return (usage.inputTokens * prices.input + usage.outputTokens * prices.output + usage.cacheReadTokens * readRate + writes.fiveMinute * write5mRate + writes.oneHour * write1hRate) / 1e6;
38607
+ }
38608
+
38323
38609
  // apps/gateway/src/dispatch/index.ts
38324
38610
  async function dispatch(request2, deps, signal, requestId) {
38611
+ const logger2 = deps.logger ?? noopLogger;
38325
38612
  const startedAt = deps.now();
38326
38613
  const snapshot = await deps.snapshots.get(startedAt);
38327
38614
  const deadlineAt = startedAt + snapshot.settings.requestDeadlineMs;
@@ -38375,8 +38662,20 @@ async function dispatch(request2, deps, signal, requestId) {
38375
38662
  now: startedAt,
38376
38663
  rand: deps.rand()
38377
38664
  });
38665
+ logger2.debug("routing candidates ranked", {
38666
+ requestId,
38667
+ requestedModel: request2.model,
38668
+ model: candidates[0]?.target.model,
38669
+ credentialId: candidates[0]?.credential.id,
38670
+ count: candidates.length
38671
+ });
38378
38672
  for (const e of excluded) {
38379
38673
  log.degradations.push(`excluded:${e.credentialId}:${e.reason}`);
38674
+ logger2.debug("routing candidate excluded", {
38675
+ requestId,
38676
+ credentialId: e.credentialId,
38677
+ reason: e.reason
38678
+ });
38380
38679
  }
38381
38680
  if (candidates.length === 0) {
38382
38681
  clearDeadline();
@@ -38407,6 +38706,13 @@ async function dispatch(request2, deps, signal, requestId) {
38407
38706
  model: candidate.target.model,
38408
38707
  credentialId: candidate.credential.id
38409
38708
  });
38709
+ logger2.debug("attempt started", {
38710
+ requestId,
38711
+ provider: candidate.target.provider,
38712
+ model: candidate.target.model,
38713
+ credentialId: candidate.credential.id,
38714
+ attempt: i + 1
38715
+ });
38410
38716
  log.inputTokens = 0;
38411
38717
  log.outputTokens = 0;
38412
38718
  log.cacheReadTokens = 0;
@@ -38428,7 +38734,9 @@ async function dispatch(request2, deps, signal, requestId) {
38428
38734
  signal: dispatchSignal,
38429
38735
  refresh: (credential) => waitForCancellation(deps.refresh(credential), dispatchSignal),
38430
38736
  refreshLeadMs: DISPATCH_REFRESH_LEAD_MS,
38431
- ...retrySecrets === undefined ? {} : { secrets: retrySecrets }
38737
+ ...retrySecrets === undefined ? {} : { secrets: retrySecrets },
38738
+ logger: logger2,
38739
+ requestId
38432
38740
  }), dispatchSignal);
38433
38741
  for (const d of result.degradations)
38434
38742
  log.degradations.push(d);
@@ -38438,6 +38746,14 @@ async function dispatch(request2, deps, signal, requestId) {
38438
38746
  if (event.type === "blockDelta" && !committed) {
38439
38747
  committed = true;
38440
38748
  log.ttftMs = deps.now() - startedAt;
38749
+ logger2.debug("stream committed", {
38750
+ requestId,
38751
+ provider: candidate.target.provider,
38752
+ model: candidate.target.model,
38753
+ credentialId: candidate.credential.id,
38754
+ attempt: i + 1,
38755
+ ttftMs: log.ttftMs
38756
+ });
38441
38757
  for (const buffered of pending)
38442
38758
  yield buffered;
38443
38759
  pending.length = 0;
@@ -38448,7 +38764,7 @@ async function dispatch(request2, deps, signal, requestId) {
38448
38764
  log.outputTokens = event.usage.outputTokens;
38449
38765
  log.cacheReadTokens = event.usage.cacheReadTokens;
38450
38766
  log.cacheWriteTokens = event.usage.cacheWriteTokens;
38451
- log.costUsd = priceOf(candidate, event.usage);
38767
+ log.costUsd = priceOf(candidate.target.costPerMTok, event.usage, candidate.target.provider);
38452
38768
  }
38453
38769
  if (event.type === "error") {
38454
38770
  terminal = true;
@@ -38502,6 +38818,14 @@ async function dispatch(request2, deps, signal, requestId) {
38502
38818
  lastError = retryAfterMs === undefined ? new GatewayError(code2, message) : new GatewayError(code2, message, { retryAfterMs });
38503
38819
  if (code2 === "AUTH" && !committed && !authRefreshRetried && !preemptiveRefreshRequired && candidate.credential.authType === "oauth" && candidate.credential.hasRefreshToken) {
38504
38820
  authRefreshRetried = true;
38821
+ logger2.warn("attempt authentication failed; refreshing credential", {
38822
+ requestId,
38823
+ provider: candidate.target.provider,
38824
+ model: candidate.target.model,
38825
+ credentialId: candidate.credential.id,
38826
+ attempt: i + 1,
38827
+ code: code2
38828
+ });
38505
38829
  try {
38506
38830
  retrySecrets = await waitForCancellation(deps.refresh(candidate.credential), dispatchSignal);
38507
38831
  continue;
@@ -38523,6 +38847,18 @@ async function dispatch(request2, deps, signal, requestId) {
38523
38847
  ...failure.retryAfterMs === undefined ? {} : { retryAfterMs: failure.retryAfterMs },
38524
38848
  jitter: deps.rand()
38525
38849
  }));
38850
+ if (!committed && RETRYABLE[failure.code] && i + 1 < maxAttempts) {
38851
+ logger2.warn("attempt failed; retrying", {
38852
+ requestId,
38853
+ provider: candidate.target.provider,
38854
+ model: candidate.target.model,
38855
+ credentialId: candidate.credential.id,
38856
+ attempt: i + 1,
38857
+ code: failure.code,
38858
+ retryable: true,
38859
+ retryAfterMs: failure.retryAfterMs
38860
+ });
38861
+ }
38526
38862
  if (committed) {
38527
38863
  log.status = HTTP_STATUS[failure.code];
38528
38864
  log.errorCode = failure.code;
@@ -38559,11 +38895,6 @@ async function dispatch(request2, deps, signal, requestId) {
38559
38895
  }
38560
38896
  return { events: run(), log: () => log };
38561
38897
  }
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
38898
  function waitForCancellation(promise2, signal) {
38568
38899
  if (signal.aborted)
38569
38900
  return Promise.reject(signal.reason);
@@ -38624,7 +38955,12 @@ async function* anthropicStream(events, requestId) {
38624
38955
  content: [],
38625
38956
  stop_reason: null,
38626
38957
  stop_sequence: null,
38627
- usage: { input_tokens: 0, output_tokens: 0 }
38958
+ usage: {
38959
+ input_tokens: 0,
38960
+ output_tokens: 0,
38961
+ cache_read_input_tokens: 0,
38962
+ cache_creation_input_tokens: 0
38963
+ }
38628
38964
  }
38629
38965
  });
38630
38966
  break;
@@ -38657,7 +38993,9 @@ async function* anthropicStream(events, requestId) {
38657
38993
  delta: { stop_reason: STOP_REASON2[event.stopReason], stop_sequence: null },
38658
38994
  usage: {
38659
38995
  input_tokens: event.usage.inputTokens,
38660
- output_tokens: event.usage.outputTokens
38996
+ output_tokens: event.usage.outputTokens,
38997
+ cache_read_input_tokens: event.usage.cacheReadTokens,
38998
+ cache_creation_input_tokens: event.usage.cacheWriteTokens
38661
38999
  }
38662
39000
  });
38663
39001
  yield frame("message_stop", { type: "message_stop" });
@@ -38693,7 +39031,9 @@ function anthropicResponse(collected, requestId) {
38693
39031
  stop_sequence: null,
38694
39032
  usage: {
38695
39033
  input_tokens: collected.usage.inputTokens,
38696
- output_tokens: collected.usage.outputTokens
39034
+ output_tokens: collected.usage.outputTokens,
39035
+ cache_read_input_tokens: collected.usage.cacheReadTokens,
39036
+ cache_creation_input_tokens: collected.usage.cacheWriteTokens
38697
39037
  }
38698
39038
  };
38699
39039
  }
@@ -38702,6 +39042,15 @@ function anthropicErrorBody(code, message) {
38702
39042
  }
38703
39043
 
38704
39044
  // apps/gateway/src/egress/openai.ts
39045
+ function usageBody(usage) {
39046
+ const prompt = promptTokens(usage);
39047
+ return {
39048
+ prompt_tokens: prompt,
39049
+ completion_tokens: usage.outputTokens,
39050
+ total_tokens: prompt + usage.outputTokens,
39051
+ prompt_tokens_details: { cached_tokens: usage.cacheReadTokens }
39052
+ };
39053
+ }
38705
39054
  var FINISH2 = {
38706
39055
  endTurn: "stop",
38707
39056
  maxTokens: "length",
@@ -38799,11 +39148,7 @@ async function* openaiStream(events, requestId, created) {
38799
39148
  break;
38800
39149
  case "end":
38801
39150
  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
- }));
39151
+ yield emit(chunk(requestId, created, model, { index: 0, delta: {}, finish_reason: FINISH2[event.stopReason] }, usageBody(event.usage)));
38807
39152
  break;
38808
39153
  case "error": {
38809
39154
  const e = ERROR_TYPE3[event.code];
@@ -38840,11 +39185,7 @@ function openaiResponse(collected, requestId, created) {
38840
39185
  finish_reason: FINISH2[collected.stopReason]
38841
39186
  }
38842
39187
  ],
38843
- usage: {
38844
- prompt_tokens: collected.usage.inputTokens,
38845
- completion_tokens: collected.usage.outputTokens,
38846
- total_tokens: collected.usage.inputTokens + collected.usage.outputTokens
38847
- }
39188
+ usage: usageBody(collected.usage)
38848
39189
  };
38849
39190
  }
38850
39191
  function openaiErrorBody(code, message) {
@@ -38853,14 +39194,19 @@ function openaiErrorBody(code, message) {
38853
39194
  }
38854
39195
 
38855
39196
  // apps/gateway/src/ingress/anthropic.ts
38856
- var textBlock = exports_external.object({ type: exports_external.literal("text"), text: exports_external.string() });
39197
+ var textBlock = exports_external.object({
39198
+ type: exports_external.literal("text"),
39199
+ text: exports_external.string(),
39200
+ cache_control: cacheControlSchema.optional()
39201
+ });
38857
39202
  var imageBlock = exports_external.object({
38858
39203
  type: exports_external.literal("image"),
38859
39204
  source: exports_external.object({
38860
39205
  type: exports_external.literal("base64"),
38861
39206
  media_type: exports_external.string(),
38862
39207
  data: exports_external.string()
38863
- })
39208
+ }),
39209
+ cache_control: cacheControlSchema.optional()
38864
39210
  });
38865
39211
  var thinkingBlock = exports_external.object({
38866
39212
  type: exports_external.literal("thinking"),
@@ -38871,13 +39217,15 @@ var toolUseBlock = exports_external.object({
38871
39217
  type: exports_external.literal("tool_use"),
38872
39218
  id: exports_external.string(),
38873
39219
  name: exports_external.string(),
38874
- input: exports_external.record(exports_external.string(), exports_external.unknown()).default({})
39220
+ input: exports_external.record(exports_external.string(), exports_external.unknown()).default({}),
39221
+ cache_control: cacheControlSchema.optional()
38875
39222
  });
38876
39223
  var toolResultBlock = exports_external.object({
38877
39224
  type: exports_external.literal("tool_result"),
38878
39225
  tool_use_id: exports_external.string(),
38879
39226
  content: exports_external.union([exports_external.string(), exports_external.array(exports_external.unknown())]).optional(),
38880
- is_error: exports_external.boolean().optional()
39227
+ is_error: exports_external.boolean().optional(),
39228
+ cache_control: cacheControlSchema.optional()
38881
39229
  });
38882
39230
  var block = exports_external.discriminatedUnion("type", [
38883
39231
  textBlock,
@@ -38901,7 +39249,8 @@ var schema = exports_external.object({
38901
39249
  tools: exports_external.array(exports_external.object({
38902
39250
  name: exports_external.string(),
38903
39251
  description: exports_external.string().optional(),
38904
- input_schema: exports_external.record(exports_external.string(), exports_external.unknown())
39252
+ input_schema: exports_external.record(exports_external.string(), exports_external.unknown()),
39253
+ cache_control: cacheControlSchema.optional()
38905
39254
  })).optional(),
38906
39255
  tool_choice: exports_external.union([
38907
39256
  exports_external.object({ type: exports_external.enum(["auto", "any", "none"]) }),
@@ -38945,9 +39294,14 @@ function flattenToolResult(content) {
38945
39294
  function toIrBlock(b) {
38946
39295
  switch (b.type) {
38947
39296
  case "text":
38948
- return { type: "text", text: b.text };
39297
+ return { type: "text", text: b.text, ...irCacheControl(b.cache_control) };
38949
39298
  case "image":
38950
- return { type: "image", mediaType: b.source.media_type, data: b.source.data };
39299
+ return {
39300
+ type: "image",
39301
+ mediaType: b.source.media_type,
39302
+ data: b.source.data,
39303
+ ...irCacheControl(b.cache_control)
39304
+ };
38951
39305
  case "thinking":
38952
39306
  return {
38953
39307
  type: "thinking",
@@ -38955,13 +39309,20 @@ function toIrBlock(b) {
38955
39309
  ...b.signature !== undefined && { signature: b.signature }
38956
39310
  };
38957
39311
  case "tool_use":
38958
- return { type: "toolUse", id: b.id, name: b.name, input: b.input };
39312
+ return {
39313
+ type: "toolUse",
39314
+ id: b.id,
39315
+ name: b.name,
39316
+ input: b.input,
39317
+ ...irCacheControl(b.cache_control)
39318
+ };
38959
39319
  case "tool_result":
38960
39320
  return {
38961
39321
  type: "toolResult",
38962
39322
  toolUseId: b.tool_use_id,
38963
39323
  content: flattenToolResult(b.content),
38964
- isError: b.is_error ?? false
39324
+ isError: b.is_error ?? false,
39325
+ ...irCacheControl(b.cache_control)
38965
39326
  };
38966
39327
  }
38967
39328
  }
@@ -39008,7 +39369,11 @@ function parseAnthropicRequest(body2, headers) {
39008
39369
  role: m.role,
39009
39370
  content: typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content.map(toIrBlock)
39010
39371
  }));
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 }));
39372
+ const system = parsed.system === undefined ? undefined : typeof parsed.system === "string" ? [{ type: "text", text: parsed.system }] : parsed.system.map((b) => ({
39373
+ type: "text",
39374
+ text: b.text,
39375
+ ...irCacheControl(b.cache_control)
39376
+ }));
39012
39377
  const request2 = {
39013
39378
  model: parsed.model,
39014
39379
  messages,
@@ -39026,7 +39391,8 @@ function parseAnthropicRequest(body2, headers) {
39026
39391
  request2.tools = parsed.tools.map((t2) => ({
39027
39392
  name: t2.name,
39028
39393
  ...t2.description !== undefined && { description: t2.description },
39029
- inputSchema: t2.input_schema
39394
+ inputSchema: t2.input_schema,
39395
+ ...irCacheControl(t2.cache_control)
39030
39396
  }));
39031
39397
  }
39032
39398
  if (parsed.tool_choice !== undefined)
@@ -39048,8 +39414,16 @@ function parseAnthropicRequest(body2, headers) {
39048
39414
 
39049
39415
  // apps/gateway/src/ingress/openai.ts
39050
39416
  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() }) })
39417
+ exports_external.object({
39418
+ type: exports_external.literal("text"),
39419
+ text: exports_external.string(),
39420
+ cache_control: exports_external.unknown().optional()
39421
+ }),
39422
+ exports_external.object({
39423
+ type: exports_external.literal("image_url"),
39424
+ image_url: exports_external.object({ url: exports_external.string() }),
39425
+ cache_control: exports_external.unknown().optional()
39426
+ })
39053
39427
  ]);
39054
39428
  var toolCall = exports_external.object({
39055
39429
  id: exports_external.string(),
@@ -39060,7 +39434,8 @@ var message2 = exports_external.object({
39060
39434
  role: exports_external.enum(["system", "developer", "user", "assistant", "tool"]),
39061
39435
  content: exports_external.union([exports_external.string(), exports_external.array(part), exports_external.null()]).optional(),
39062
39436
  tool_calls: exports_external.array(toolCall).optional(),
39063
- tool_call_id: exports_external.string().optional()
39437
+ tool_call_id: exports_external.string().optional(),
39438
+ cache_control: exports_external.unknown().optional()
39064
39439
  });
39065
39440
  var schema2 = exports_external.object({
39066
39441
  model: exports_external.string().min(1),
@@ -39075,8 +39450,10 @@ var schema2 = exports_external.object({
39075
39450
  function: exports_external.object({
39076
39451
  name: exports_external.string(),
39077
39452
  description: exports_external.string().optional(),
39078
- parameters: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
39079
- })
39453
+ parameters: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
39454
+ cache_control: exports_external.unknown().optional()
39455
+ }),
39456
+ cache_control: exports_external.unknown().optional()
39080
39457
  })).optional(),
39081
39458
  tool_choice: exports_external.union([
39082
39459
  exports_external.enum(["auto", "none", "required"]),
@@ -39114,11 +39491,15 @@ function contentBlocks(content) {
39114
39491
  return [];
39115
39492
  return content.map((p) => {
39116
39493
  if (p.type === "text")
39117
- return { type: "text", text: p.text };
39494
+ return { type: "text", text: p.text, ...looseCacheControl(p.cache_control) };
39118
39495
  const { mediaType, data } = parseDataUrl(p.image_url.url);
39119
- return { type: "image", mediaType, data };
39496
+ return { type: "image", mediaType, data, ...looseCacheControl(p.cache_control) };
39120
39497
  });
39121
39498
  }
39499
+ function toolCacheControl(outer, inner) {
39500
+ const parsed = looseCacheControl(outer);
39501
+ return parsed.cacheControl === undefined ? looseCacheControl(inner) : parsed;
39502
+ }
39122
39503
  function toIrToolChoice2(c) {
39123
39504
  if (typeof c === "string") {
39124
39505
  if (c === "required")
@@ -39136,7 +39517,9 @@ function parseOpenAIRequest(body2) {
39136
39517
  const messages = [];
39137
39518
  for (const m of parsed.messages) {
39138
39519
  if (m.role === "system" || m.role === "developer") {
39139
- system.push(...contentBlocks(m.content));
39520
+ const blocks = contentBlocks(m.content);
39521
+ applyMessageCacheControl(blocks, m.cache_control);
39522
+ system.push(...blocks);
39140
39523
  continue;
39141
39524
  }
39142
39525
  if (m.role === "tool") {
@@ -39150,7 +39533,8 @@ function parseOpenAIRequest(body2) {
39150
39533
  type: "toolResult",
39151
39534
  toolUseId: m.tool_call_id,
39152
39535
  content: typeof m.content === "string" ? m.content : "",
39153
- isError: false
39536
+ isError: false,
39537
+ ...looseCacheControl(m.cache_control)
39154
39538
  }
39155
39539
  ]
39156
39540
  });
@@ -39165,6 +39549,7 @@ function parseOpenAIRequest(body2) {
39165
39549
  input: parseArguments(call.function.arguments)
39166
39550
  });
39167
39551
  }
39552
+ applyMessageCacheControl(content, m.cache_control);
39168
39553
  if (content.length > 0)
39169
39554
  messages.push({ role: m.role, content });
39170
39555
  }
@@ -39190,7 +39575,8 @@ function parseOpenAIRequest(body2) {
39190
39575
  request2.tools = parsed.tools.map((t2) => ({
39191
39576
  name: t2.function.name,
39192
39577
  ...t2.function.description !== undefined && { description: t2.function.description },
39193
- inputSchema: t2.function.parameters ?? { type: "object" }
39578
+ inputSchema: t2.function.parameters ?? { type: "object" },
39579
+ ...toolCacheControl(t2.cache_control, t2.function.cache_control)
39194
39580
  }));
39195
39581
  }
39196
39582
  if (parsed.tool_choice !== undefined)
@@ -39203,6 +39589,85 @@ function parseOpenAIRequest(body2) {
39203
39589
  return validateRequest(request2);
39204
39590
  }
39205
39591
 
39592
+ // apps/gateway/src/routes/models.ts
39593
+ var CREATED_AT = new Date(0).toISOString();
39594
+ function narrower(a, b) {
39595
+ const context = [a.contextWindow, b.contextWindow].filter((n) => n !== undefined);
39596
+ const output = [a.maxOutputTokens, b.maxOutputTokens].filter((n) => n !== undefined);
39597
+ return {
39598
+ ...context.length === 0 ? {} : { contextWindow: Math.min(...context) },
39599
+ ...output.length === 0 ? {} : { maxOutputTokens: Math.min(...output) }
39600
+ };
39601
+ }
39602
+ function targetLimits(target, auths) {
39603
+ const ways = auths.size === 0 ? ["apiKey"] : [...auths];
39604
+ let listed = {};
39605
+ for (const auth of ways) {
39606
+ const entry = catalogLimits(target.provider, target.model, auth);
39607
+ if (entry === null)
39608
+ continue;
39609
+ listed = narrower(listed, {
39610
+ contextWindow: entry.contextWindow,
39611
+ maxOutputTokens: entry.maxOutputTokens
39612
+ });
39613
+ }
39614
+ const contextWindow = target.contextWindow ?? listed.contextWindow;
39615
+ const maxOutputTokens = target.maxOutputTokens ?? listed.maxOutputTokens;
39616
+ return {
39617
+ ...contextWindow === undefined ? {} : { contextWindow },
39618
+ ...maxOutputTokens === undefined ? {} : { maxOutputTokens }
39619
+ };
39620
+ }
39621
+ function limitsOf(model, authsByProvider) {
39622
+ let limits = {};
39623
+ for (const target of model.targets) {
39624
+ limits = narrower(limits, targetLimits(target, authsByProvider.get(target.provider) ?? new Set));
39625
+ }
39626
+ return limits;
39627
+ }
39628
+ function displayName(model) {
39629
+ const only = model.targets.length === 1 ? model.targets[0] : undefined;
39630
+ if (only === undefined)
39631
+ return model.id;
39632
+ const labelled = PROVIDER_MODEL_CATALOG[only.provider]?.models.find((choice) => choice.id === only.model);
39633
+ return labelled?.label ?? model.id;
39634
+ }
39635
+ function servingAuths(credentials) {
39636
+ const byProvider = new Map;
39637
+ for (const credential of credentials) {
39638
+ if (!credential.enabled)
39639
+ continue;
39640
+ const ways = byProvider.get(credential.provider) ?? new Set;
39641
+ ways.add(credential.authType);
39642
+ byProvider.set(credential.provider, ways);
39643
+ }
39644
+ return byProvider;
39645
+ }
39646
+ function describeModel(model, credentials) {
39647
+ const limits = limitsOf(model, servingAuths(credentials));
39648
+ return {
39649
+ id: model.id,
39650
+ object: "model",
39651
+ type: "model",
39652
+ display_name: displayName(model),
39653
+ created: 0,
39654
+ created_at: CREATED_AT,
39655
+ owned_by: "omnigateway",
39656
+ ...limits.contextWindow === undefined ? {} : { max_input_tokens: limits.contextWindow },
39657
+ ...limits.maxOutputTokens === undefined ? {} : { max_tokens: limits.maxOutputTokens }
39658
+ };
39659
+ }
39660
+ function modelListBody(models, credentials) {
39661
+ const data = models.map((model) => describeModel(model, credentials));
39662
+ return {
39663
+ object: "list",
39664
+ data,
39665
+ has_more: false,
39666
+ first_id: data[0]?.id ?? null,
39667
+ last_id: data[data.length - 1]?.id ?? null
39668
+ };
39669
+ }
39670
+
39206
39671
  // apps/gateway/src/routes/proxy.ts
39207
39672
  var KEEPALIVE_MS = 1e4;
39208
39673
  var KEEPALIVE = Symbol("keepalive");
@@ -39234,9 +39699,9 @@ function asGatewayError(error51) {
39234
39699
  function sseResponse(frames, onDone, keepaliveMs) {
39235
39700
  const encoder2 = new TextEncoder;
39236
39701
  let done = null;
39237
- const runOnce = () => {
39702
+ const runOnce = (cancelled = false) => {
39238
39703
  if (done === null)
39239
- done = onDone();
39704
+ done = onDone(cancelled);
39240
39705
  return done;
39241
39706
  };
39242
39707
  let inflight = null;
@@ -39269,7 +39734,7 @@ data: ${data}
39269
39734
  },
39270
39735
  async cancel() {
39271
39736
  await frames.return(undefined);
39272
- await runOnce();
39737
+ await runOnce(true);
39273
39738
  }
39274
39739
  });
39275
39740
  return new Response(stream2, { headers: SSE_HEADERS });
@@ -39280,9 +39745,34 @@ async function handle(deps, rateLimiter, surface, request2) {
39280
39745
  let keyId = null;
39281
39746
  let requestedModel = "";
39282
39747
  try {
39283
- const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
39748
+ let key;
39749
+ try {
39750
+ key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
39751
+ } catch (error51) {
39752
+ if (error51 instanceof GatewayError && error51.code === "AUTH") {
39753
+ deps.logger.warn("authentication rejected", {
39754
+ requestId,
39755
+ surface,
39756
+ reason: "invalid credentials"
39757
+ });
39758
+ }
39759
+ throw error51;
39760
+ }
39284
39761
  keyId = key.id;
39285
- rateLimiter.consume(key.id, key.rateLimitPerMin);
39762
+ try {
39763
+ rateLimiter.consume(key.id, key.rateLimitPerMin);
39764
+ } catch (error51) {
39765
+ if (error51 instanceof GatewayError && error51.code === "RATE_LIMIT") {
39766
+ deps.logger.warn("rate limit rejected", {
39767
+ requestId,
39768
+ surface,
39769
+ apiKeyId: key.id,
39770
+ code: error51.code,
39771
+ retryAfterMs: error51.retryAfterMs
39772
+ });
39773
+ }
39774
+ throw error51;
39775
+ }
39286
39776
  const body2 = await request2.json();
39287
39777
  const chatRequest = surface === "anthropic" ? parseAnthropicRequest(body2, request2.headers) : parseOpenAIRequest(body2);
39288
39778
  if (key.modelAllowlist !== null && !key.modelAllowlist.includes(chatRequest.model)) {
@@ -39294,7 +39784,7 @@ async function handle(deps, rateLimiter, surface, request2) {
39294
39784
  ...deps,
39295
39785
  async onRoute(target) {
39296
39786
  if (began) {
39297
- await routeLog(deps.store, requestId, target);
39787
+ await routeLog(deps.store, requestId, target, deps.logger);
39298
39788
  return;
39299
39789
  }
39300
39790
  began = true;
@@ -39306,10 +39796,45 @@ async function handle(deps, rateLimiter, surface, request2) {
39306
39796
  resolvedModel: target.model,
39307
39797
  credentialId: target.credentialId
39308
39798
  });
39309
- await beginLog(deps.store, log2, keyId);
39799
+ await beginLog(deps.store, log2, keyId, deps.logger);
39310
39800
  }
39311
39801
  }, request2.signal, requestId);
39312
- const log = () => finishLog(deps.store, outcome.log(), keyId);
39802
+ const log = async (cancelled = false) => {
39803
+ const completed = outcome.log();
39804
+ const wasCancelled = cancelled || request2.signal.aborted;
39805
+ if (wasCancelled && completed.status === 0) {
39806
+ completed.status = 499;
39807
+ completed.errorCode = "interrupted";
39808
+ completed.durationMs = deps.now() - startedAt;
39809
+ }
39810
+ await finishLog(deps.store, completed, keyId, deps.logger);
39811
+ const fields = {
39812
+ requestId,
39813
+ surface,
39814
+ status: completed.status,
39815
+ provider: completed.resolvedProvider ?? undefined,
39816
+ model: completed.resolvedModel ?? undefined,
39817
+ requestedModel: completed.requestedModel,
39818
+ credentialId: completed.credentialId ?? undefined,
39819
+ apiKeyId: keyId ?? undefined,
39820
+ attempts: completed.attempts,
39821
+ code: completed.errorCode ?? undefined,
39822
+ stream: chatRequest.stream,
39823
+ inputTokens: completed.inputTokens,
39824
+ outputTokens: completed.outputTokens,
39825
+ cacheReadTokens: completed.cacheReadTokens,
39826
+ cacheWriteTokens: completed.cacheWriteTokens,
39827
+ costUsd: completed.costUsd,
39828
+ ttftMs: completed.ttftMs,
39829
+ durationMs: completed.durationMs
39830
+ };
39831
+ if (wasCancelled)
39832
+ deps.logger.debug("request cancelled", fields);
39833
+ else if (completed.status >= 400)
39834
+ deps.logger.error("request failed", fields);
39835
+ else
39836
+ deps.logger.info("request done", fields);
39837
+ };
39313
39838
  if (chatRequest.stream) {
39314
39839
  const frames = surface === "anthropic" ? anthropicStream(outcome.events, requestId) : openaiStream(outcome.events, requestId, Math.floor(deps.now() / 1000));
39315
39840
  return sseResponse(frames, log, deps.keepaliveMs);
@@ -39329,41 +39854,59 @@ async function handle(deps, rateLimiter, surface, request2) {
39329
39854
  });
39330
39855
  } catch (error51) {
39331
39856
  const gatewayError = asGatewayError(error51);
39332
- await finishLog(deps.store, newCompletedRequestLog(requestId, startedAt, {
39857
+ const completed = newCompletedRequestLog(requestId, startedAt, {
39333
39858
  requestedModel,
39334
39859
  status: HTTP_STATUS[gatewayError.code],
39335
- errorCode: gatewayError.code
39336
- }), keyId);
39860
+ errorCode: gatewayError.code,
39861
+ durationMs: deps.now() - startedAt
39862
+ });
39863
+ await finishLog(deps.store, completed, keyId, deps.logger);
39864
+ deps.logger.error("request failed", {
39865
+ requestId,
39866
+ surface,
39867
+ status: completed.status,
39868
+ requestedModel,
39869
+ apiKeyId: keyId ?? undefined,
39870
+ code: gatewayError.code,
39871
+ attempts: completed.attempts,
39872
+ durationMs: completed.durationMs,
39873
+ reason: gatewayError.message
39874
+ });
39337
39875
  return errorResponse(surface, gatewayError.code, gatewayError.message);
39338
39876
  }
39339
39877
  }
39340
39878
  function proxyRoutes(deps) {
39341
39879
  const rateLimiter = deps.rateLimiter ?? new ApiKeyRateLimiter(deps.now);
39880
+ const logger2 = deps.logger ?? noopLogger;
39342
39881
  const dispatchDeps = {
39343
39882
  ...deps,
39344
- snapshots: deps.snapshots ?? createRoutingSnapshotCache(deps.store),
39883
+ logger: logger2,
39884
+ snapshots: deps.snapshots ?? createRoutingSnapshotCache(deps.store, logger2),
39345
39885
  keepaliveMs: deps.keepaliveMs ?? KEEPALIVE_MS
39346
39886
  };
39347
39887
  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
39888
  try {
39349
39889
  const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
39350
- const models = await deps.store.config.listModels();
39890
+ const snapshot = await dispatchDeps.snapshots.get(deps.now());
39891
+ const models = [...snapshot.models.values()];
39351
39892
  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
- });
39893
+ return Response.json(modelListBody(visibleModels, snapshot.credentials));
39361
39894
  } catch (error51) {
39362
39895
  const gatewayError = asGatewayError(error51);
39896
+ logger2.error("model listing failed", {
39897
+ status: HTTP_STATUS[gatewayError.code],
39898
+ code: gatewayError.code,
39899
+ reason: gatewayError.message
39900
+ });
39363
39901
  return errorResponse("anthropic", gatewayError.code, gatewayError.message);
39364
39902
  }
39365
39903
  }).onError(({ error: error51 }) => {
39366
39904
  const gatewayError = asGatewayError(error51);
39905
+ logger2.error("unhandled proxy route error", {
39906
+ status: HTTP_STATUS[gatewayError.code],
39907
+ code: gatewayError.code,
39908
+ reason: gatewayError.message
39909
+ });
39367
39910
  return errorResponse("anthropic", gatewayError.code, gatewayError.message);
39368
39911
  });
39369
39912
  }
@@ -39372,18 +39915,20 @@ function proxyRoutes(deps) {
39372
39915
  var ADMIN_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
39373
39916
  function createApp(deps) {
39374
39917
  const now = deps.now ?? (() => Date.now());
39918
+ const logger2 = deps.logger ?? noopLogger;
39375
39919
  const rand = deps.rand ?? Math.random;
39376
- const http = deps.http ?? nodeHttpClient();
39920
+ const http = deps.http ?? nodeHttpClient({ logger: logger2, now });
39377
39921
  const adapters = deps.adapters ?? ADAPTERS;
39378
39922
  const requestId = deps.requestId ?? (() => `req_${crypto.randomUUID()}`);
39379
39923
  const rateLimiter = new ApiKeyRateLimiter(now);
39380
- const snapshots = createRoutingSnapshotCache(deps.store);
39924
+ const snapshots = createRoutingSnapshotCache(deps.store, logger2);
39381
39925
  const admin = createAdminAuth(deps.store, { now, sessionTtlMs: ADMIN_SESSION_TTL_MS });
39382
39926
  const refresh = deps.refresh ?? createRefresher({
39383
39927
  store: deps.store,
39384
39928
  providers: OAUTH_PROVIDERS,
39385
39929
  http,
39386
- now
39930
+ now,
39931
+ logger: logger2
39387
39932
  });
39388
39933
  const staticDir = deps.staticDir ? resolve(deps.staticDir) : undefined;
39389
39934
  let staticRoot;
@@ -39401,13 +39946,21 @@ function createApp(deps) {
39401
39946
  rand,
39402
39947
  refresh,
39403
39948
  requestId,
39404
- rateLimiter
39405
- })).use(adminRoutes({ store: deps.store, admin, now, sessionTtlMs: ADMIN_SESSION_TTL_MS })).use(connectRoutes({
39949
+ rateLimiter,
39950
+ logger: logger2
39951
+ })).use(adminRoutes({
39952
+ store: deps.store,
39953
+ admin,
39954
+ now,
39955
+ sessionTtlMs: ADMIN_SESSION_TTL_MS,
39956
+ logger: logger2
39957
+ })).use(connectRoutes({
39406
39958
  store: deps.store,
39407
39959
  admin,
39408
39960
  providers: OAUTH_PROVIDERS,
39409
39961
  http,
39410
- now
39962
+ now,
39963
+ logger: logger2
39411
39964
  })).get("/*", ({ path, request: request2, set: set2 }) => {
39412
39965
  const rawPath = new URL(request2.url).pathname;
39413
39966
  const error51 = () => {
@@ -39465,13 +40018,15 @@ var DAY_MS = 24 * 60 * 60 * 1000;
39465
40018
  var ROLLUP_RETENTION_DAYS = 400;
39466
40019
  async function pruneLogs(store, now) {
39467
40020
  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);
40021
+ const raw = await store.usage.prune(now - settings.logRetentionDays * DAY_MS);
40022
+ const daily = await store.usage.pruneDaily(now - ROLLUP_RETENTION_DAYS * DAY_MS);
40023
+ return { raw, daily };
39470
40024
  }
39471
40025
  function startMaintenance(deps) {
40026
+ const logger2 = deps.logger ?? noopLogger;
39472
40027
  const timer = setInterval(() => {
39473
- pruneLogs(deps.store, deps.now()).catch((error51) => {
39474
- console.error("log pruning failed", {
40028
+ pruneLogs(deps.store, deps.now()).then(({ raw, daily }) => logger2.debug("request logs pruned", { rawCount: raw, dailyCount: daily })).catch((error51) => {
40029
+ logger2.error("log pruning failed", {
39475
40030
  reason: error51 instanceof Error ? error51.message : "unknown"
39476
40031
  });
39477
40032
  });
@@ -39486,6 +40041,7 @@ function due(credentials, now) {
39486
40041
  return credentials.filter((c) => c.enabled && c.authType === "oauth" && c.expiresAt !== null && c.expiresAt - SCHEDULER_REFRESH_LEAD_MS <= now);
39487
40042
  }
39488
40043
  async function sweep(deps) {
40044
+ const logger2 = deps.logger ?? noopLogger;
39489
40045
  const credentials = await deps.store.credentials.list();
39490
40046
  let refreshed = 0;
39491
40047
  for (const credential of due(credentials, deps.now())) {
@@ -39504,23 +40060,25 @@ async function sweep(deps) {
39504
40060
  refreshed += 1;
39505
40061
  } catch (error51) {
39506
40062
  const code = error51 instanceof GatewayError ? error51.code : "INTERNAL";
39507
- console.warn("scheduled token refresh failed", {
39508
- credentialId: credential.id,
40063
+ logger2.warn("scheduled token refresh failed", {
39509
40064
  provider: credential.provider,
39510
- code
40065
+ credentialId: credential.id,
40066
+ code,
40067
+ reason: error51 instanceof Error ? error51.message : "unknown"
39511
40068
  });
39512
40069
  }
39513
40070
  }
39514
40071
  return refreshed;
39515
40072
  }
39516
40073
  function startRefreshScheduler(deps) {
40074
+ const logger2 = deps.logger ?? noopLogger;
39517
40075
  let running = false;
39518
40076
  const timer = setInterval(() => {
39519
40077
  if (running)
39520
40078
  return;
39521
40079
  running = true;
39522
40080
  sweep(deps).catch((error51) => {
39523
- console.error("token refresh sweep failed", {
40081
+ logger2.error("token refresh sweep failed", {
39524
40082
  reason: error51 instanceof Error ? error51.message : "unknown"
39525
40083
  });
39526
40084
  }).finally(() => {
@@ -39533,6 +40091,7 @@ function startRefreshScheduler(deps) {
39533
40091
 
39534
40092
  // apps/gateway/src/quota/poller.ts
39535
40093
  async function startQuotaPoller(deps) {
40094
+ const logger2 = deps.logger ?? noopLogger;
39536
40095
  const { quotaPollIntervalMs } = await deps.store.config.getSettings();
39537
40096
  if (quotaPollIntervalMs <= 0)
39538
40097
  return () => {};
@@ -39542,7 +40101,7 @@ async function startQuotaPoller(deps) {
39542
40101
  return;
39543
40102
  running = true;
39544
40103
  poll(deps).catch((error51) => {
39545
- console.error("quota poll failed", {
40104
+ logger2.error("quota poll failed", {
39546
40105
  reason: error51 instanceof Error ? error51.message : "unknown"
39547
40106
  });
39548
40107
  }).finally(() => {
@@ -39556,62 +40115,101 @@ async function startQuotaPoller(deps) {
39556
40115
  }
39557
40116
 
39558
40117
  // 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);
40118
+ function stdoutLogger(level) {
40119
+ return createLogger({
40120
+ level,
40121
+ color: Boolean(process.stdout.isTTY),
40122
+ write: (line) => {
40123
+ process.stdout.write(`${line}
40124
+ `);
40125
+ }
40126
+ });
40127
+ }
40128
+ var logger2 = stdoutLogger("info");
40129
+ async function main() {
40130
+ const config2 = loadConfig(process.env);
40131
+ logger2 = stdoutLogger(config2.logLevel);
40132
+ if (config2.logLevelFallbackFrom !== null) {
40133
+ logger2.info("invalid log level; using info", { reason: config2.logLevelFallbackFrom });
40134
+ }
40135
+ logger2.info("omnigateway booting", { host: config2.host, port: config2.port });
40136
+ function dashboardDir() {
40137
+ if (config2.staticDir !== null)
40138
+ return config2.staticDir;
40139
+ const candidates = [
40140
+ resolve2(import.meta.dir, "./public"),
40141
+ resolve2(import.meta.dir, "../../dashboard/dist")
40142
+ ];
40143
+ return candidates.find((path) => existsSync(path)) ?? candidates[1];
40144
+ }
40145
+ const encryptionKey = await deriveKey(config2.encryptionKey);
40146
+ const store = await createStore({
40147
+ path: config2.databasePath,
40148
+ encryptionKey,
40149
+ logger: logger2
40150
+ });
40151
+ const swept = await store.usage.sweepPending();
40152
+ if (swept > 0)
40153
+ logger2.info("retired interrupted requests", { count: swept });
40154
+ const now = () => Date.now();
40155
+ const http = nodeHttpClient({ logger: logger2, now });
40156
+ const refresh = createRefresher({ store, providers: OAUTH_PROVIDERS, http, now, logger: logger2 });
40157
+ const staticDir = dashboardDir();
40158
+ logger2.info(existsSync(staticDir) ? "dashboard directory resolved" : "dashboard directory absent", {
40159
+ path: staticDir
40160
+ });
40161
+ const app = createApp({
40162
+ store,
40163
+ baseUrl: config2.baseUrl,
40164
+ http,
40165
+ now,
40166
+ refresh,
40167
+ staticDir,
40168
+ logger: logger2
40169
+ });
40170
+ const stopMaintenance = startMaintenance({ store, now, logger: logger2 });
40171
+ const stopRefreshScheduler = startRefreshScheduler({ store, refresh, now, logger: logger2 });
40172
+ const stopQuotaPoller = await startQuotaPoller({
40173
+ store,
40174
+ providers: OAUTH_PROVIDERS,
40175
+ http,
40176
+ refresh,
40177
+ now,
40178
+ logger: logger2
40179
+ });
40180
+ app.listen({ port: config2.port, hostname: config2.host, idleTimeout: 255 });
40181
+ logger2.info("omnigateway listening", { host: config2.host, port: config2.port });
40182
+ let shuttingDown = false;
40183
+ function exitAfterClosingStore(code) {
40184
+ try {
40185
+ store.close();
40186
+ } finally {
40187
+ process.exit(code);
40188
+ }
40189
+ }
40190
+ for (const signal of ["SIGINT", "SIGTERM"]) {
40191
+ process.on(signal, () => {
40192
+ if (shuttingDown)
40193
+ exitAfterClosingStore(1);
40194
+ logger2.info("shutdown requested", { reason: signal });
40195
+ shuttingDown = true;
40196
+ stopMaintenance();
40197
+ stopRefreshScheduler();
40198
+ stopQuotaPoller();
40199
+ app.stop().then(() => exitAfterClosingStore(0), (error51) => {
40200
+ logger2.error("shutdown failed", {
40201
+ reason: error51 instanceof Error ? error51.message : "unknown"
40202
+ });
40203
+ return exitAfterClosingStore(1);
40204
+ });
40205
+ });
39605
40206
  }
39606
40207
  }
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));
40208
+ try {
40209
+ await main();
40210
+ } catch (error51) {
40211
+ logger2.error("gateway boot failed", {
40212
+ reason: error51 instanceof Error ? error51.message : "unknown"
39616
40213
  });
40214
+ process.exit(1);
39617
40215
  }