omnigateway 0.6.0 → 0.7.0

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
@@ -6795,6 +6795,7 @@ var MIGRATIONS = [
6795
6795
  function openDb(path) {
6796
6796
  const db = new Database(path, { create: true });
6797
6797
  db.run("PRAGMA journal_mode = WAL");
6798
+ db.run("PRAGMA synchronous = NORMAL");
6798
6799
  db.run("PRAGMA foreign_keys = ON");
6799
6800
  db.run("PRAGMA busy_timeout = 5000");
6800
6801
  db.run("CREATE TABLE IF NOT EXISTS migrations (id INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL)");
@@ -7163,6 +7164,18 @@ function estimateInputTokens(request) {
7163
7164
  total += toolTokens(tool);
7164
7165
  return total;
7165
7166
  }
7167
+ function estimateInputPrefixes(request) {
7168
+ let tools = 0;
7169
+ for (const tool of request.tools ?? [])
7170
+ tools += toolTokens(tool);
7171
+ let toolsAndSystem = tools;
7172
+ for (const block of request.system ?? [])
7173
+ toolsAndSystem += blockTokens(block);
7174
+ let total = toolsAndSystem;
7175
+ for (const message of request.messages)
7176
+ total += messageTokens(message);
7177
+ return { tools, toolsAndSystem, total };
7178
+ }
7166
7179
  function estimateCachedInputTokens(request) {
7167
7180
  let running = 0;
7168
7181
  let cached = 0;
@@ -24697,7 +24710,7 @@ import { join as join3 } from "path";
24697
24710
 
24698
24711
  // packages/plugin-api/src/version.ts
24699
24712
  var PLUGIN_API_VERSION = 2;
24700
- var DASHBOARD_SDK_VERSION = "0.1.3";
24713
+ var DASHBOARD_SDK_VERSION = "0.1.4";
24701
24714
  // packages/plugin-api/src/manifest.ts
24702
24715
  var CAPABILITIES = [
24703
24716
  "storage",
@@ -25427,47 +25440,77 @@ var PROFILES = {
25427
25440
  Object.setPrototypeOf(PROFILES, null);
25428
25441
 
25429
25442
  // packages/providers/src/sse.ts
25443
+ var HOLDBACK = 2;
25444
+ var MAX_RECORD_CHARS = 10 * 1024 * 1024;
25430
25445
  async function* parseSse(body) {
25431
25446
  const reader = body.getReader();
25432
25447
  const decoder3 = new TextDecoder;
25433
- let buf = "";
25448
+ const pending = [];
25449
+ let pendingChars = 0;
25450
+ let tail = "";
25434
25451
  try {
25435
25452
  while (true) {
25436
25453
  const { done, value } = await reader.read();
25437
25454
  if (done)
25438
25455
  break;
25439
- buf += decoder3.decode(value, { stream: true });
25440
- buf = buf.replaceAll(`\r
25441
- `, `
25442
- `);
25443
- let sep2 = buf.indexOf(`
25444
-
25456
+ const probe = tail + decoder3.decode(value, { stream: true });
25457
+ let start = 0;
25458
+ let nl = probe.indexOf(`
25445
25459
  `);
25446
- while (sep2 !== -1) {
25447
- const record2 = buf.slice(0, sep2);
25448
- buf = buf.slice(sep2 + 2);
25460
+ while (nl !== -1) {
25461
+ const end = separatorEnd(probe, nl);
25462
+ if (end === -1) {
25463
+ nl = probe.indexOf(`
25464
+ `, nl + 1);
25465
+ continue;
25466
+ }
25467
+ refuseIfOversized(pendingChars + (nl - start));
25468
+ pending.push(probe.slice(start, nl));
25469
+ const record2 = pending.join("");
25470
+ pending.length = 0;
25471
+ pendingChars = 0;
25449
25472
  const msg = parseRecord(record2);
25450
25473
  if (msg)
25451
25474
  yield msg;
25452
- sep2 = buf.indexOf(`
25453
-
25454
- `);
25455
- }
25456
- }
25457
- const tail = parseRecord(buf.replaceAll(`\r
25458
- `, `
25459
- `));
25460
- if (tail)
25461
- yield tail;
25475
+ start = end;
25476
+ nl = probe.indexOf(`
25477
+ `, end);
25478
+ }
25479
+ const keep2 = Math.min(HOLDBACK, probe.length - start);
25480
+ const settled = probe.slice(start, probe.length - keep2);
25481
+ if (settled.length > 0) {
25482
+ pending.push(settled);
25483
+ pendingChars += settled.length;
25484
+ }
25485
+ tail = probe.slice(probe.length - keep2);
25486
+ refuseIfOversized(pendingChars + tail.length);
25487
+ }
25488
+ const last = parseRecord(pending.join("") + tail);
25489
+ if (last)
25490
+ yield last;
25462
25491
  } finally {
25463
25492
  reader.releaseLock();
25464
25493
  }
25465
25494
  }
25495
+ function refuseIfOversized(chars) {
25496
+ if (chars <= MAX_RECORD_CHARS)
25497
+ return;
25498
+ throw new GatewayError("UPSTREAM", `upstream SSE record exceeded ${MAX_RECORD_CHARS} characters`, { gatewayAuthored: true });
25499
+ }
25500
+ function separatorEnd(text, at) {
25501
+ const next = text.charCodeAt(at + 1);
25502
+ if (next === 10)
25503
+ return at + 2;
25504
+ if (next === 13 && text.charCodeAt(at + 2) === 10)
25505
+ return at + 3;
25506
+ return -1;
25507
+ }
25466
25508
  function parseRecord(record2) {
25467
25509
  let event = "message";
25468
25510
  const data = [];
25469
- for (const line of record2.split(`
25511
+ for (const rawLine of record2.split(`
25470
25512
  `)) {
25513
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
25471
25514
  if (line.length === 0 || line.startsWith(":"))
25472
25515
  continue;
25473
25516
  const colon = line.indexOf(":");
@@ -25959,7 +26002,7 @@ async function* decodeAnthropic(messages, opts = {}) {
25959
26002
  case "message_delta": {
25960
26003
  const reason = d.delta?.stop_reason;
25961
26004
  if (typeof reason === "string") {
25962
- const mapped = STOP_REASON[reason];
26005
+ const mapped = Object.hasOwn(STOP_REASON, reason) ? STOP_REASON[reason] : undefined;
25963
26006
  if (mapped === undefined) {
25964
26007
  yield protocolError(`unrecognized Anthropic stop reason "${reason}"`);
25965
26008
  return;
@@ -25989,7 +26032,7 @@ async function* decodeAnthropic(messages, opts = {}) {
25989
26032
  terminal = true;
25990
26033
  const type = String(d.error?.type);
25991
26034
  const message = String(d.error?.message ?? "upstream error");
25992
- const code = isFingerprintRefusal(type, message) ? "FINGERPRINT_REFUSED" : ERROR_TYPE[type] ?? "UPSTREAM";
26035
+ const code = isFingerprintRefusal(type, message) ? "FINGERPRINT_REFUSED" : (Object.hasOwn(ERROR_TYPE, type) ? ERROR_TYPE[type] : undefined) ?? "UPSTREAM";
25993
26036
  yield { type: "error", code, message, retryable: RETRYABLE[code] };
25994
26037
  break;
25995
26038
  }
@@ -26195,14 +26238,15 @@ function addAutoCacheBreakpoints(body, req, note) {
26195
26238
  let markedPrefix = 0;
26196
26239
  const worthAMarker = (prefix) => prefix - markedPrefix >= AUTO_CACHE_MIN_TOKENS;
26197
26240
  let stablePrefixMarked = false;
26198
- const toolsPrefix = estimateInputTokens({ ...req, messages: [], system: [] });
26241
+ const prefixes = estimateInputPrefixes(req);
26242
+ const toolsPrefix = prefixes.tools;
26199
26243
  const lastTool = body.tools?.at(-1);
26200
26244
  if (lastTool !== undefined && worthAMarker(toolsPrefix)) {
26201
26245
  lastTool.cache_control = { type: "ephemeral" };
26202
26246
  markedPrefix = toolsPrefix;
26203
26247
  stablePrefixMarked = true;
26204
26248
  }
26205
- const systemPrefix = estimateInputTokens({ ...req, messages: [] });
26249
+ const systemPrefix = prefixes.toolsAndSystem;
26206
26250
  const lastSystem = body.system?.at(-1);
26207
26251
  if (lastSystem !== undefined && worthAMarker(systemPrefix)) {
26208
26252
  lastSystem.cache_control = { type: "ephemeral" };
@@ -26211,7 +26255,7 @@ function addAutoCacheBreakpoints(body, req, note) {
26211
26255
  }
26212
26256
  if (stablePrefixMarked)
26213
26257
  note("anthropic:cache-breakpoint-added");
26214
- if (!worthAMarker(estimateInputTokens(req)))
26258
+ if (!worthAMarker(prefixes.total))
26215
26259
  return;
26216
26260
  const lastHistory = lastCacheableHistoryBlock(body.messages);
26217
26261
  if (lastHistory === undefined)
@@ -26506,7 +26550,18 @@ async function* decodeCustomChat(messages) {
26506
26550
  }
26507
26551
  }
26508
26552
  if (typeof choice.finish_reason === "string") {
26509
- stopReason = CHAT_FINISH[choice.finish_reason] ?? "endTurn";
26553
+ const reason = choice.finish_reason;
26554
+ const mapped = Object.hasOwn(CHAT_FINISH, reason) ? CHAT_FINISH[reason] : undefined;
26555
+ if (mapped === undefined) {
26556
+ yield {
26557
+ type: "error",
26558
+ code: "UPSTREAM",
26559
+ message: `unrecognized custom endpoint finish reason "${reason}"`,
26560
+ retryable: false
26561
+ };
26562
+ return;
26563
+ }
26564
+ stopReason = mapped;
26510
26565
  }
26511
26566
  }
26512
26567
  if (done) {
@@ -26530,6 +26585,32 @@ var RESPONSES_ERROR_CODE = {
26530
26585
  context_length_exceeded: "BAD_REQUEST",
26531
26586
  content_policy_violation: "CONTENT_FILTER"
26532
26587
  };
26588
+ var KNOWN_RESPONSES_EVENTS = new Set([
26589
+ "response.created",
26590
+ "response.queued",
26591
+ "response.in_progress",
26592
+ "response.output_item.added",
26593
+ "response.output_item.done",
26594
+ "response.content_part.added",
26595
+ "response.content_part.done",
26596
+ "response.output_text.delta",
26597
+ "response.output_text.done",
26598
+ "response.output_text.annotation.added",
26599
+ "response.refusal.delta",
26600
+ "response.refusal.done",
26601
+ "response.reasoning_summary_part.added",
26602
+ "response.reasoning_summary_part.done",
26603
+ "response.reasoning_summary_text.delta",
26604
+ "response.reasoning_summary_text.done",
26605
+ "response.reasoning_text.delta",
26606
+ "response.reasoning_text.done",
26607
+ "response.function_call_arguments.delta",
26608
+ "response.function_call_arguments.done",
26609
+ "response.completed",
26610
+ "response.incomplete",
26611
+ "response.failed",
26612
+ "error"
26613
+ ]);
26533
26614
  async function* decodeCustomResponses(messages) {
26534
26615
  const indices = new Map;
26535
26616
  let next = 0;
@@ -26546,6 +26627,17 @@ async function* decodeCustomResponses(messages) {
26546
26627
  let terminal = false;
26547
26628
  const ownsBlock = new Set;
26548
26629
  for await (const msg of messages) {
26630
+ if (msg.data === "[DONE]")
26631
+ continue;
26632
+ if (!KNOWN_RESPONSES_EVENTS.has(msg.event)) {
26633
+ yield {
26634
+ type: "error",
26635
+ code: "UPSTREAM",
26636
+ message: `unrecognized custom endpoint stream event "${msg.event}"`,
26637
+ retryable: false
26638
+ };
26639
+ return;
26640
+ }
26549
26641
  const d = json3(msg.data);
26550
26642
  if (d === null)
26551
26643
  continue;
@@ -26627,6 +26719,15 @@ async function* decodeCustomResponses(messages) {
26627
26719
  stopReason = "maxTokens";
26628
26720
  else if (reason === "content_filter")
26629
26721
  stopReason = "contentFilter";
26722
+ else if (reason !== undefined || msg.event === "response.incomplete" || r.status !== undefined && r.status !== "completed") {
26723
+ yield {
26724
+ type: "error",
26725
+ code: "UPSTREAM",
26726
+ message: reason !== undefined ? `unrecognized custom endpoint incomplete reason "${String(reason)}"` : r.status !== undefined && r.status !== "incomplete" ? `custom endpoint reported terminal response status "${String(r.status)}"` : "custom endpoint reported the response incomplete without a reason",
26727
+ retryable: false
26728
+ };
26729
+ break;
26730
+ }
26630
26731
  yield {
26631
26732
  type: "end",
26632
26733
  stopReason,
@@ -26638,7 +26739,8 @@ async function* decodeCustomResponses(messages) {
26638
26739
  case "error": {
26639
26740
  terminal = true;
26640
26741
  const err = d.response?.error ?? d.error ?? {};
26641
- const code = RESPONSES_ERROR_CODE[String(err.code ?? err.type)] ?? "UPSTREAM";
26742
+ const raw = String(err.code ?? err.type);
26743
+ const code = (Object.hasOwn(RESPONSES_ERROR_CODE, raw) ? RESPONSES_ERROR_CODE[raw] : undefined) ?? "UPSTREAM";
26642
26744
  yield {
26643
26745
  type: "error",
26644
26746
  code,
@@ -27091,6 +27193,15 @@ async function* decodeGrokResponses(messages) {
27091
27193
  stopReason = "maxTokens";
27092
27194
  else if (reason === "content_filter")
27093
27195
  stopReason = "contentFilter";
27196
+ else if (reason !== undefined || msg.event === "response.incomplete" || r.status !== undefined && r.status !== "completed") {
27197
+ yield {
27198
+ type: "error",
27199
+ code: "UPSTREAM",
27200
+ message: reason !== undefined ? `unrecognized xAI incomplete reason "${String(reason)}"` : r.status !== undefined && r.status !== "incomplete" ? `xAI reported terminal response status "${String(r.status)}"` : "xAI reported the response incomplete without a reason",
27201
+ retryable: false
27202
+ };
27203
+ break;
27204
+ }
27094
27205
  yield {
27095
27206
  type: "end",
27096
27207
  stopReason,
@@ -27102,7 +27213,8 @@ async function* decodeGrokResponses(messages) {
27102
27213
  case "error": {
27103
27214
  terminal = true;
27104
27215
  const err = d.response?.error ?? d.error ?? {};
27105
- const code = ERROR_CODE[String(err.code ?? err.type)] ?? "UPSTREAM";
27216
+ const raw = String(err.code ?? err.type);
27217
+ const code = (Object.hasOwn(ERROR_CODE, raw) ? ERROR_CODE[raw] : undefined) ?? "UPSTREAM";
27106
27218
  yield {
27107
27219
  type: "error",
27108
27220
  code,
@@ -27518,7 +27630,18 @@ async function* decodeKiloChat(messages) {
27518
27630
  }
27519
27631
  }
27520
27632
  if (typeof choice.finish_reason === "string") {
27521
- stopReason = FINISH[choice.finish_reason] ?? "endTurn";
27633
+ const reason = choice.finish_reason;
27634
+ const mapped = Object.hasOwn(FINISH, reason) ? FINISH[reason] : undefined;
27635
+ if (mapped === undefined) {
27636
+ yield {
27637
+ type: "error",
27638
+ code: "UPSTREAM",
27639
+ message: `unrecognized Kilo finish reason "${reason}"`,
27640
+ retryable: false
27641
+ };
27642
+ return;
27643
+ }
27644
+ stopReason = mapped;
27522
27645
  }
27523
27646
  }
27524
27647
  if (done) {
@@ -27805,7 +27928,18 @@ async function* decodeChat(messages) {
27805
27928
  }
27806
27929
  }
27807
27930
  if (typeof choice.finish_reason === "string") {
27808
- stopReason = FINISH2[choice.finish_reason] ?? "endTurn";
27931
+ const reason = choice.finish_reason;
27932
+ const mapped = Object.hasOwn(FINISH2, reason) ? FINISH2[reason] : undefined;
27933
+ if (mapped === undefined) {
27934
+ yield {
27935
+ type: "error",
27936
+ code: "UPSTREAM",
27937
+ message: `unrecognized Kimi finish reason "${reason}"`,
27938
+ retryable: false
27939
+ };
27940
+ return;
27941
+ }
27942
+ stopReason = mapped;
27809
27943
  }
27810
27944
  }
27811
27945
  if (done) {
@@ -27963,6 +28097,32 @@ var ERROR_CODE2 = {
27963
28097
  context_length_exceeded: "BAD_REQUEST",
27964
28098
  content_policy_violation: "CONTENT_FILTER"
27965
28099
  };
28100
+ var KNOWN_EVENTS3 = new Set([
28101
+ "response.created",
28102
+ "response.queued",
28103
+ "response.in_progress",
28104
+ "response.output_item.added",
28105
+ "response.output_item.done",
28106
+ "response.content_part.added",
28107
+ "response.content_part.done",
28108
+ "response.output_text.delta",
28109
+ "response.output_text.done",
28110
+ "response.output_text.annotation.added",
28111
+ "response.refusal.delta",
28112
+ "response.refusal.done",
28113
+ "response.reasoning_summary_part.added",
28114
+ "response.reasoning_summary_part.done",
28115
+ "response.reasoning_summary_text.delta",
28116
+ "response.reasoning_summary_text.done",
28117
+ "response.reasoning_text.delta",
28118
+ "response.reasoning_text.done",
28119
+ "response.function_call_arguments.delta",
28120
+ "response.function_call_arguments.done",
28121
+ "response.completed",
28122
+ "response.incomplete",
28123
+ "response.failed",
28124
+ "error"
28125
+ ]);
27966
28126
  function json7(data) {
27967
28127
  try {
27968
28128
  const v = JSON.parse(data);
@@ -27987,6 +28147,17 @@ async function* decodeResponses(messages) {
27987
28147
  let terminal = false;
27988
28148
  const ownsBlock = new Set;
27989
28149
  for await (const msg of messages) {
28150
+ if (msg.data === "[DONE]")
28151
+ continue;
28152
+ if (!KNOWN_EVENTS3.has(msg.event)) {
28153
+ yield {
28154
+ type: "error",
28155
+ code: "UPSTREAM",
28156
+ message: `unrecognized OpenAI stream event "${msg.event}"`,
28157
+ retryable: false
28158
+ };
28159
+ return;
28160
+ }
27990
28161
  const d = json7(msg.data);
27991
28162
  if (d === null)
27992
28163
  continue;
@@ -28068,6 +28239,15 @@ async function* decodeResponses(messages) {
28068
28239
  stopReason = "maxTokens";
28069
28240
  else if (reason === "content_filter")
28070
28241
  stopReason = "contentFilter";
28242
+ else if (reason !== undefined || msg.event === "response.incomplete" || r.status !== undefined && r.status !== "completed") {
28243
+ yield {
28244
+ type: "error",
28245
+ code: "UPSTREAM",
28246
+ message: reason !== undefined ? `unrecognized OpenAI incomplete reason "${String(reason)}"` : r.status !== undefined && r.status !== "incomplete" ? `OpenAI reported terminal response status "${String(r.status)}"` : "OpenAI reported the response incomplete without a reason",
28247
+ retryable: false
28248
+ };
28249
+ break;
28250
+ }
28071
28251
  yield {
28072
28252
  type: "end",
28073
28253
  stopReason,
@@ -28079,7 +28259,8 @@ async function* decodeResponses(messages) {
28079
28259
  case "error": {
28080
28260
  terminal = true;
28081
28261
  const err = d.response?.error ?? d.error ?? {};
28082
- const code = ERROR_CODE2[String(err.code ?? err.type)] ?? "UPSTREAM";
28262
+ const raw = String(err.code ?? err.type);
28263
+ const code = (Object.hasOwn(ERROR_CODE2, raw) ? ERROR_CODE2[raw] : undefined) ?? "UPSTREAM";
28083
28264
  yield {
28084
28265
  type: "error",
28085
28266
  code,
@@ -44438,7 +44619,7 @@ class SlidingWindow {
44438
44619
  aged++;
44439
44620
  }
44440
44621
  if (aged > 0)
44441
- this.stamps = this.stamps.slice(aged);
44622
+ this.stamps.splice(0, aged);
44442
44623
  return this.stamps.length;
44443
44624
  }
44444
44625
  record(now) {
@@ -44533,6 +44714,7 @@ function retryAfterMs(violation, now) {
44533
44714
  // apps/gateway/src/auth/rateLimit.ts
44534
44715
  var LONG_WINDOWS = ["5h", "1w"];
44535
44716
  var CACHE_TTL_MS = 30000;
44717
+ var CLEANUP_INTERVAL_MS = 1000;
44536
44718
  var EAGER_FRACTION = 0.9;
44537
44719
  var MAX_DEBITS = 1e4;
44538
44720
 
@@ -44604,6 +44786,7 @@ function trimDebits(debits, now) {
44604
44786
 
44605
44787
  class ApiKeyRateLimiter {
44606
44788
  keys = new Map;
44789
+ lastCleanup = Number.NEGATIVE_INFINITY;
44607
44790
  store;
44608
44791
  now;
44609
44792
  logger;
@@ -44694,6 +44877,9 @@ class ApiKeyRateLimiter {
44694
44877
  pendingDebits(keyId) {
44695
44878
  return this.keys.get(keyId)?.debits.length ?? 0;
44696
44879
  }
44880
+ liveKeys() {
44881
+ return this.keys.size;
44882
+ }
44697
44883
  async refuse(keyId, decision, now, requestId) {
44698
44884
  const violation = decision.violation;
44699
44885
  if (violation === null)
@@ -44803,6 +44989,10 @@ class ApiKeyRateLimiter {
44803
44989
  }
44804
44990
  }
44805
44991
  cleanup(now) {
44992
+ const since = now - this.lastCleanup;
44993
+ if (since >= 0 && since < CLEANUP_INTERVAL_MS)
44994
+ return;
44995
+ this.lastCleanup = now;
44806
44996
  for (const [keyId, state] of this.keys) {
44807
44997
  state.ring.count(now);
44808
44998
  if (state.deciding > 0 || !state.ring.empty || state.inFlight > 0)
@@ -46340,6 +46530,9 @@ function extractToken(header) {
46340
46530
 
46341
46531
  // apps/gateway/src/bodyCapture.ts
46342
46532
  var MAX_CAPTURED_BODY_BYTES = MAX_ARTIFACT_BYTES;
46533
+ function textOf(entry) {
46534
+ return entry.responseChunks.join("");
46535
+ }
46343
46536
  function asBody(text) {
46344
46537
  if (text.length === 0)
46345
46538
  return null;
@@ -46394,18 +46587,18 @@ function createBodyCollector(options) {
46394
46587
  continue;
46395
46588
  }
46396
46589
  bytes += value.byteLength;
46397
- entry.responseText += decoder3.decode(value, { stream: true });
46590
+ entry.responseChunks.push(decoder3.decode(value, { stream: true }));
46398
46591
  if (bytes > MAX_CAPTURED_BODY_BYTES)
46399
46592
  entry.truncated = true;
46400
46593
  }
46401
- entry.responseText += decoder3.decode();
46594
+ entry.responseChunks.push(decoder3.decode());
46402
46595
  } catch {
46403
46596
  entry.truncated = true;
46404
46597
  } finally {
46405
46598
  reader.releaseLock();
46406
46599
  }
46407
46600
  if (options.captureStreamChunks)
46408
- entry.frames = framesOf(entry.responseText);
46601
+ entry.frames = framesOf(textOf(entry));
46409
46602
  };
46410
46603
  const captureResponse = (res, entry) => {
46411
46604
  let forAdapter;
@@ -46428,11 +46621,12 @@ function createBodyCollector(options) {
46428
46621
  text: async () => {
46429
46622
  const text = await res.text();
46430
46623
  entry.responded = true;
46431
- entry.responseText = text.slice(0, MAX_CAPTURED_BODY_BYTES);
46432
- if (entry.responseText.length < text.length)
46624
+ const kept = text.slice(0, MAX_CAPTURED_BODY_BYTES);
46625
+ entry.responseChunks = [kept];
46626
+ if (kept.length < text.length)
46433
46627
  entry.truncated = true;
46434
46628
  if (options.captureStreamChunks)
46435
- entry.frames = framesOf(entry.responseText);
46629
+ entry.frames = framesOf(kept);
46436
46630
  return text;
46437
46631
  }
46438
46632
  };
@@ -46445,7 +46639,7 @@ function createBodyCollector(options) {
46445
46639
  attempt: attempts.length + 1,
46446
46640
  provider: req.provider,
46447
46641
  request: req.body.length > MAX_CAPTURED_BODY_BYTES ? req.body.slice(0, MAX_CAPTURED_BODY_BYTES) : asBody(req.body),
46448
- responseText: "",
46642
+ responseChunks: [],
46449
46643
  responded: false,
46450
46644
  frames: null,
46451
46645
  truncated: req.body.length > MAX_CAPTURED_BODY_BYTES
@@ -46460,7 +46654,7 @@ function createBodyCollector(options) {
46460
46654
  attempt: entry.attempt,
46461
46655
  provider: entry.provider,
46462
46656
  request: entry.request,
46463
- response: entry.responded ? asBody(entry.responseText) : null,
46657
+ response: entry.responded ? asBody(textOf(entry)) : null,
46464
46658
  streamChunks: entry.frames,
46465
46659
  truncated: entry.truncated
46466
46660
  }));
@@ -50318,6 +50512,9 @@ function streamRoutes(deps) {
50318
50512
  return;
50319
50513
  }
50320
50514
  if (frame2.type === "unsubscribe") {
50515
+ if (topicClass(frame2.topic) === "plugin" && deps.registry.has(id, frame2.topic)) {
50516
+ deps.channels.closed(id, [frame2.topic]);
50517
+ }
50321
50518
  deps.registry.unsubscribe(id, frame2.topic);
50322
50519
  send(ws, { ...head, type: "ack", topic: frame2.topic });
50323
50520
  return;
@@ -50590,7 +50787,7 @@ function createSocketRegistry(deps = {}) {
50590
50787
  break;
50591
50788
  let status2;
50592
50789
  try {
50593
- status2 = connection.socket.send(JSON.stringify(frame2));
50790
+ status2 = connection.socket.send(frame2);
50594
50791
  } catch {
50595
50792
  return;
50596
50793
  }
@@ -50599,6 +50796,13 @@ function createSocketRegistry(deps = {}) {
50599
50796
  connection.queue.shift();
50600
50797
  }
50601
50798
  };
50799
+ const encode4 = (frame2) => {
50800
+ try {
50801
+ return JSON.stringify(frame2);
50802
+ } catch {
50803
+ return null;
50804
+ }
50805
+ };
50602
50806
  const deliver = (connection, frame2) => {
50603
50807
  if (connection.queue.length >= capacity) {
50604
50808
  connection.queue.shift();
@@ -50746,16 +50950,22 @@ function createSocketRegistry(deps = {}) {
50746
50950
  const subscribers = index.get(topic);
50747
50951
  if (subscribers === undefined)
50748
50952
  return;
50953
+ const payload = encode4(frame2);
50954
+ if (payload === null)
50955
+ return;
50749
50956
  for (const id of subscribers) {
50750
50957
  const connection = connections.get(id);
50751
50958
  if (connection !== undefined)
50752
- deliver(connection, frame2);
50959
+ deliver(connection, payload);
50753
50960
  }
50754
50961
  },
50755
50962
  sendTo(id, frame2) {
50756
50963
  const connection = connections.get(id);
50757
- if (connection !== undefined)
50758
- deliver(connection, frame2);
50964
+ if (connection === undefined)
50965
+ return;
50966
+ const payload = encode4(frame2);
50967
+ if (payload !== null)
50968
+ deliver(connection, payload);
50759
50969
  },
50760
50970
  closeAll(code, reason) {
50761
50971
  for (const connection of [...connections.values()])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnigateway",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Self-hosted AI gateway with Anthropic- and OpenAI-compatible APIs, an admin console, and a CLI",
5
5
  "license": "MIT",
6
6
  "author": "Harismawan <mail@harismawan.com>",
@@ -1,4 +1,4 @@
1
- import{A as e,it as t,z as n}from"./Lamp-DN2EKL4m.js";import{a as r,i,r as a,t as o,v as s,y as c}from"./Panel-BCpZVY4U.js";import{_ as l,t as u}from"./Rack-B03tpeW-.js";import{a as d,i as f,r as p}from"./stream-BXI9HD3l.js";import{i as m}from"./Field-DI5YTYOX.js";import{useQueryClient as h}from"@tanstack/react-query";import{useLayoutEffect as g,useMemo as _,useRef as v,useState as y}from"react";import{jsx as b,jsxs as x}from"react/jsx-runtime";import S from"styled-components";var C={debug:10,info:20,warn:30,error:40};function w(e){return typeof e==`string`&&e in C}function T(e,t){return!w(t)||e.level===null||C[e.level]>=C[t]}function E(e){if(typeof e!=`object`||!e)return null;let{raw:t,at:n,level:r,msg:i}=e;return typeof t!=`string`||n!==null&&typeof n!=`number`||r!==null&&!w(r)||i!==null&&typeof i!=`string`?null:{raw:t,at:n,level:r,msg:i}}function D(e){if(typeof e!=`object`||!e)return null;let{lines:t}=e;if(!Array.isArray(t))return null;let n=[];for(let e of t){let t=E(e);if(t===null)return null;n.push(t)}return n}var O=[100,200,500],k=[{value:``,label:`All levels`},{value:`debug`,label:`Debug and above`},{value:`info`,label:`Info and above`},{value:`warn`,label:`Warnings and errors`},{value:`error`,label:`Errors only`}],A=S(c)`
1
+ import{A as e,it as t,z as n}from"./Lamp-DN2EKL4m.js";import{a as r,i,r as a,t as o,v as s,y as c}from"./Panel-BCpZVY4U.js";import{_ as l,t as u}from"./Rack-B03tpeW-.js";import{a as d,i as f,r as p}from"./stream-BD4FhzLn.js";import{i as m}from"./Field-DI5YTYOX.js";import{useQueryClient as h}from"@tanstack/react-query";import{useLayoutEffect as g,useMemo as _,useRef as v,useState as y}from"react";import{jsx as b,jsxs as x}from"react/jsx-runtime";import S from"styled-components";var C={debug:10,info:20,warn:30,error:40};function w(e){return typeof e==`string`&&e in C}function T(e,t){return!w(t)||e.level===null||C[e.level]>=C[t]}function E(e){if(typeof e!=`object`||!e)return null;let{raw:t,at:n,level:r,msg:i}=e;return typeof t!=`string`||n!==null&&typeof n!=`number`||r!==null&&!w(r)||i!==null&&typeof i!=`string`?null:{raw:t,at:n,level:r,msg:i}}function D(e){if(typeof e!=`object`||!e)return null;let{lines:t}=e;if(!Array.isArray(t))return null;let n=[];for(let e of t){let t=E(e);if(t===null)return null;n.push(t)}return n}var O=[100,200,500],k=[{value:``,label:`All levels`},{value:`debug`,label:`Debug and above`},{value:`info`,label:`Info and above`},{value:`warn`,label:`Warnings and errors`},{value:`error`,label:`Errors only`}],A=S(c)`
2
2
  gap: ${({theme:e})=>e.space(2)};
3
3
  flex-wrap: wrap;
4
4
  `,j=S(m)`
@@ -41,4 +41,4 @@ import{A as e,it as t,z as n}from"./Lamp-DN2EKL4m.js";import{a as r,i,r as a,t a
41
41
  `,R=S.code`
42
42
  font-family: ${({theme:e})=>e.font.mono};
43
43
  word-break: break-all;
44
- `;function z({read:e}){return e.source===`file`?x(L,{children:[`Reading `,b(R,{children:e.path}),`, named by `,b(R,{children:`OMNI_LOG_FILE`}),`. That variable says where the gateway's output is being captured; it does not redirect it.`]}):e.source===`journal`?x(L,{children:[`Reading the systemd journal for `,b(R,{children:`omnigateway.service`}),`. To read a file instead, redirect the gateway's output to one and point `,b(R,{children:`OMNI_LOG_FILE`}),` at the same path.`]}):null}var B=[],V={read:void 0,lines:B};function H(){let{cadence:o}=l(),c=h(),[m,S]=y(200),[C,w]=y(``),E=t(m,C,o(n,f)),L=E.data,[R,H]=y(V),W=R.read===L?R.lines:B,G=_(()=>[...L?.lines??[],...W].slice(-m),[L,W,m]),K=v(null),q=v(!0);return p(f,e=>{let t=e.kind===`frame`?D(e.payload):null;if(t===null){H({read:L,lines:B}),e.kind===`frame`&&d(c,f);return}let n=t.filter(e=>T(e,C));n.length!==0&&H(e=>({read:L,lines:[...e.read===L?e.lines:[],...n].slice(-m)}))}),g(()=>{let e=K.current;e!==null&&q.current&&G.length>0&&(e.scrollTop=e.scrollHeight)},[G]),x(M,{children:[b(u,{legend:`Console`,title:`Gateway output`,summary:E.isLoading?`Reading the gateway's own output…`:`${e(G.length)} lines of what this process is doing. Requests are in Logs; prompt bodies, tokens and keys are never written here.`,actions:x(A,{children:[b(j,{value:C,"aria-label":`Which levels to show`,onChange:e=>w(e.target.value),children:k.map(e=>b(`option`,{value:e.value,children:e.label},e.value))}),b(j,{value:m,"aria-label":`How many lines to fetch`,onChange:e=>S(Number(e.target.value)),children:O.map(e=>x(`option`,{value:e,children:[`last `,e]},e))})]})}),x(N,{legend:`Process output`,meta:L===void 0?void 0:b(s,{children:U(L)}),flush:!0,children:[L===void 0?null:b(z,{read:L}),b(P,{ref:K,"data-testid":`console-terminal`,onScroll:e=>{let t=e.currentTarget;q.current=t.scrollHeight-t.scrollTop-t.clientHeight<=8},children:E.isError?b(i,{error:E.error,onRetry:()=>void E.refetch()}):E.isLoading?b(`div`,{style:{padding:12},children:b(r,{rows:10})}):L!==void 0&&L.source===`none`?b(a,{legend:`Nothing is capturing this gateway`,message:"Its output is going to a terminal, so there is nothing to read back. To capture it, run the gateway under systemd with `omni service install`, or start it with `omni start`, which redirects output to a file and points OMNI_LOG_FILE at it."}):G.length===0?b(a,{legend:`Nothing to show`,message:C===``?`This log is empty. The gateway writes here when it boots, refreshes a token, or polls a quota.`:`No line in this window is at that level. Widen the filter to see everything.`}):b(F,{children:G.map((e,t)=>b(I,{$level:e.level,children:e.raw},`${e.at??0}-${t}`))})})]})]})}function U(e){return e.source===`file`?`log file`:e.source===`journal`?`systemd journal`:`not captured`}var W=H;export{W as component};
44
+ `;function z({read:e}){return e.source===`file`?x(L,{children:[`Reading `,b(R,{children:e.path}),`, named by `,b(R,{children:`OMNI_LOG_FILE`}),`. That variable says where the gateway's output is being captured; it does not redirect it.`]}):e.source===`journal`?x(L,{children:[`Reading the systemd journal for `,b(R,{children:`omnigateway.service`}),`. To read a file instead, redirect the gateway's output to one and point `,b(R,{children:`OMNI_LOG_FILE`}),` at the same path.`]}):null}var B=[],V={read:void 0,lines:B};function H(){let{cadence:o}=l(),c=h(),[m,S]=y(200),[C,w]=y(``),E=t(m,C,o(n,f)),L=E.data,[R,H]=y(V),W=R.read===L?R.lines:B,G=_(()=>[...L?.lines??[],...W].slice(-m),[L,W,m]),K=v(null),q=v(!0);return p(f,e=>{if(e.kind!==`frame`&&e.kind!==`gap`)return;let t=e.kind===`frame`?D(e.payload):null;if(t===null){H({read:L,lines:B}),e.kind===`frame`&&d(c,f);return}let n=t.filter(e=>T(e,C));n.length!==0&&H(e=>({read:L,lines:[...e.read===L?e.lines:[],...n].slice(-m)}))}),g(()=>{let e=K.current;e!==null&&q.current&&G.length>0&&(e.scrollTop=e.scrollHeight)},[G]),x(M,{children:[b(u,{legend:`Console`,title:`Gateway output`,summary:E.isLoading?`Reading the gateway's own output…`:`${e(G.length)} lines of what this process is doing. Requests are in Logs; prompt bodies, tokens and keys are never written here.`,actions:x(A,{children:[b(j,{value:C,"aria-label":`Which levels to show`,onChange:e=>w(e.target.value),children:k.map(e=>b(`option`,{value:e.value,children:e.label},e.value))}),b(j,{value:m,"aria-label":`How many lines to fetch`,onChange:e=>S(Number(e.target.value)),children:O.map(e=>x(`option`,{value:e,children:[`last `,e]},e))})]})}),x(N,{legend:`Process output`,meta:L===void 0?void 0:b(s,{children:U(L)}),flush:!0,children:[L===void 0?null:b(z,{read:L}),b(P,{ref:K,"data-testid":`console-terminal`,onScroll:e=>{let t=e.currentTarget;q.current=t.scrollHeight-t.scrollTop-t.clientHeight<=8},children:E.isError?b(i,{error:E.error,onRetry:()=>void E.refetch()}):E.isLoading?b(`div`,{style:{padding:12},children:b(r,{rows:10})}):L!==void 0&&L.source===`none`?b(a,{legend:`Nothing is capturing this gateway`,message:"Its output is going to a terminal, so there is nothing to read back. To capture it, run the gateway under systemd with `omni service install`, or start it with `omni start`, which redirects output to a file and points OMNI_LOG_FILE at it."}):G.length===0?b(a,{legend:`Nothing to show`,message:C===``?`This log is empty. The gateway writes here when it boots, refreshes a token, or polls a quota.`:`No line in this window is at that level. Widen the filter to see everything.`}):b(F,{children:G.map((e,t)=>b(I,{$level:e.level,children:e.raw},`${e.at??0}-${t}`))})})]})]})}function U(e){return e.source===`file`?`log file`:e.source===`journal`?`systemd journal`:`not captured`}var W=H;export{W as component};