reasonix 0.20.0 → 0.21.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/dist/index.d.ts CHANGED
@@ -1631,6 +1631,13 @@ declare class StreamableHttpTransport implements McpTransport {
1631
1631
  private pushMessage;
1632
1632
  }
1633
1633
 
1634
+ /** Per-server ring-buffered latency tracker; emits a "slow" event on threshold cross only. */
1635
+ interface SlowEvent {
1636
+ serverName: string;
1637
+ p95Ms: number;
1638
+ sampleSize: number;
1639
+ }
1640
+
1634
1641
  interface BridgeOptions {
1635
1642
  /** Prefix for tool names — disambiguates collisions when bridging multiple servers. */
1636
1643
  namePrefix?: string;
@@ -1647,6 +1654,12 @@ interface BridgeOptions {
1647
1654
  total?: number;
1648
1655
  message?: string;
1649
1656
  }) => void;
1657
+ /** Server name used to tag latency samples + slow events. Falls through to namePrefix without trailing `_`. */
1658
+ serverName?: string;
1659
+ /** p95 cutoff in ms before a slow event fires — defaults to 4000. */
1660
+ slowThresholdMs?: number;
1661
+ /** Fired exactly when the per-server p95 transitions over `slowThresholdMs`. */
1662
+ onSlow?: (ev: SlowEvent) => void;
1650
1663
  }
1651
1664
  declare const DEFAULT_MAX_RESULT_CHARS = 32000;
1652
1665
  /** ~6% of DeepSeek V3 context. Char cap alone fails on CJK (~1 char/token). */
@@ -1807,6 +1820,8 @@ interface ReasonixConfig {
1807
1820
  reasoningEffort?: ReasoningEffort;
1808
1821
  /** Stored as `--mcp`-format strings so one parser handles both flag and config. */
1809
1822
  mcp?: string[];
1823
+ /** Names of servers in `mcp` to skip on bridge — see `/mcp disable <name>`. */
1824
+ mcpDisabled?: string[];
1810
1825
  session?: string | null;
1811
1826
  setupCompleted?: boolean;
1812
1827
  search?: boolean;
package/dist/index.js CHANGED
@@ -1029,6 +1029,39 @@ function hasDotKey(obj) {
1029
1029
  return false;
1030
1030
  }
1031
1031
 
1032
+ // src/mcp/latency.ts
1033
+ var SAMPLE_SIZE = 5;
1034
+ var DEFAULT_THRESHOLD_MS = 4e3;
1035
+ var LatencyTracker = class {
1036
+ constructor(serverName, opts = {}) {
1037
+ this.serverName = serverName;
1038
+ this.thresholdMs = opts.thresholdMs ?? DEFAULT_THRESHOLD_MS;
1039
+ this.onSlow = opts.onSlow;
1040
+ }
1041
+ serverName;
1042
+ samples = [];
1043
+ wasOverThreshold = false;
1044
+ thresholdMs;
1045
+ onSlow;
1046
+ record(elapsedMs) {
1047
+ this.samples.push(elapsedMs);
1048
+ if (this.samples.length > SAMPLE_SIZE) this.samples.shift();
1049
+ if (this.samples.length < SAMPLE_SIZE) return;
1050
+ const p95 = computeP95(this.samples);
1051
+ const nowOver = p95 > this.thresholdMs;
1052
+ if (nowOver && !this.wasOverThreshold) {
1053
+ this.onSlow?.({ serverName: this.serverName, p95Ms: p95, sampleSize: this.samples.length });
1054
+ }
1055
+ this.wasOverThreshold = nowOver;
1056
+ }
1057
+ };
1058
+ function computeP95(samples) {
1059
+ if (samples.length === 0) return 0;
1060
+ const sorted = [...samples].sort((a, b) => a - b);
1061
+ const idx = Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95));
1062
+ return sorted[idx] ?? 0;
1063
+ }
1064
+
1032
1065
  // src/mcp/registry.ts
1033
1066
  var DEFAULT_MAX_RESULT_CHARS = 32e3;
1034
1067
  var DEFAULT_MAX_RESULT_TOKENS = 8e3;
@@ -1037,6 +1070,8 @@ async function bridgeMcpTools(client, opts = {}) {
1037
1070
  const prefix = opts.namePrefix ?? "";
1038
1071
  const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS;
1039
1072
  const result = { registry, registeredNames: [], skipped: [] };
1073
+ const serverName = opts.serverName ?? prefix.replace(/_$/, "") ?? "anon";
1074
+ const tracker = opts.onSlow ? new LatencyTracker(serverName, { thresholdMs: opts.slowThresholdMs, onSlow: opts.onSlow }) : null;
1040
1075
  const listed = await client.listTools();
1041
1076
  for (const mcpTool of listed.tools) {
1042
1077
  if (!mcpTool.name) {
@@ -1049,6 +1084,7 @@ async function bridgeMcpTools(client, opts = {}) {
1049
1084
  description: mcpTool.description ?? "",
1050
1085
  parameters: mcpTool.inputSchema,
1051
1086
  fn: async (args, ctx) => {
1087
+ const t0 = tracker ? Date.now() : 0;
1052
1088
  const toolResult = await client.callTool(mcpTool.name, args, {
1053
1089
  // Forward server-side progress frames to the bridge caller,
1054
1090
  // tagged with the registered name so multi-server UIs can
@@ -1062,6 +1098,7 @@ async function bridgeMcpTools(client, opts = {}) {
1062
1098
  // pending promise immediately, no "wait for subprocess".
1063
1099
  signal: ctx?.signal
1064
1100
  });
1101
+ if (tracker) tracker.record(Date.now() - t0);
1065
1102
  return flattenMcpResult(toolResult, { maxChars: maxResultChars });
1066
1103
  }
1067
1104
  });