reasonix 0.20.0 → 0.22.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/cli/index.js +1001 -530
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +46 -1
- package/dist/index.js +67 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1631,6 +1631,26 @@ 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
|
+
interface LatencyTrackerOptions {
|
|
1641
|
+
thresholdMs?: number;
|
|
1642
|
+
onSlow?: (ev: SlowEvent) => void;
|
|
1643
|
+
}
|
|
1644
|
+
declare class LatencyTracker {
|
|
1645
|
+
private readonly serverName;
|
|
1646
|
+
private samples;
|
|
1647
|
+
private wasOverThreshold;
|
|
1648
|
+
private readonly thresholdMs;
|
|
1649
|
+
private readonly onSlow?;
|
|
1650
|
+
constructor(serverName: string, opts?: LatencyTrackerOptions);
|
|
1651
|
+
record(elapsedMs: number): void;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1634
1654
|
interface BridgeOptions {
|
|
1635
1655
|
/** Prefix for tool names — disambiguates collisions when bridging multiple servers. */
|
|
1636
1656
|
namePrefix?: string;
|
|
@@ -1647,6 +1667,18 @@ interface BridgeOptions {
|
|
|
1647
1667
|
total?: number;
|
|
1648
1668
|
message?: string;
|
|
1649
1669
|
}) => void;
|
|
1670
|
+
/** Server name used to tag latency samples + slow events. Falls through to namePrefix without trailing `_`. */
|
|
1671
|
+
serverName?: string;
|
|
1672
|
+
/** p95 cutoff in ms before a slow event fires — defaults to 4000. */
|
|
1673
|
+
slowThresholdMs?: number;
|
|
1674
|
+
/** Fired exactly when the per-server p95 transitions over `slowThresholdMs`. */
|
|
1675
|
+
onSlow?: (ev: SlowEvent) => void;
|
|
1676
|
+
/** Indirection so reconnect can swap the underlying client without re-registering tools. */
|
|
1677
|
+
host?: McpClientHost;
|
|
1678
|
+
}
|
|
1679
|
+
/** Mutable holder so `/mcp reconnect` can swap the underlying client without re-bridging tools. */
|
|
1680
|
+
interface McpClientHost {
|
|
1681
|
+
client: McpClient;
|
|
1650
1682
|
}
|
|
1651
1683
|
declare const DEFAULT_MAX_RESULT_CHARS = 32000;
|
|
1652
1684
|
/** ~6% of DeepSeek V3 context. Char cap alone fails on CJK (~1 char/token). */
|
|
@@ -1661,7 +1693,18 @@ interface BridgeResult {
|
|
|
1661
1693
|
reason: string;
|
|
1662
1694
|
}>;
|
|
1663
1695
|
}
|
|
1664
|
-
|
|
1696
|
+
/** Resolved bridge environment that `registerSingleMcpTool` needs. Stored on summaries so reconnect can append new tools later. */
|
|
1697
|
+
interface BridgeEnv {
|
|
1698
|
+
registry: ToolRegistry;
|
|
1699
|
+
host: McpClientHost;
|
|
1700
|
+
prefix: string;
|
|
1701
|
+
maxResultChars: number;
|
|
1702
|
+
tracker: LatencyTracker | null;
|
|
1703
|
+
onProgress?: BridgeOptions["onProgress"];
|
|
1704
|
+
}
|
|
1705
|
+
declare function bridgeMcpTools(client: McpClient, opts?: BridgeOptions): Promise<BridgeResult & {
|
|
1706
|
+
env: BridgeEnv;
|
|
1707
|
+
}>;
|
|
1665
1708
|
interface FlattenOptions {
|
|
1666
1709
|
/** Cap the flattened string at this many characters. Default: no cap. */
|
|
1667
1710
|
maxChars?: number;
|
|
@@ -1807,6 +1850,8 @@ interface ReasonixConfig {
|
|
|
1807
1850
|
reasoningEffort?: ReasoningEffort;
|
|
1808
1851
|
/** Stored as `--mcp`-format strings so one parser handles both flag and config. */
|
|
1809
1852
|
mcp?: string[];
|
|
1853
|
+
/** Names of servers in `mcp` to skip on bridge — see `/mcp disable <name>`. */
|
|
1854
|
+
mcpDisabled?: string[];
|
|
1810
1855
|
session?: string | null;
|
|
1811
1856
|
setupCompleted?: boolean;
|
|
1812
1857
|
search?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -1029,45 +1029,88 @@ 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;
|
|
1068
|
+
function registerSingleMcpTool(mcpTool, env) {
|
|
1069
|
+
if (!mcpTool.name) return "";
|
|
1070
|
+
const registeredName = `${env.prefix}${mcpTool.name}`;
|
|
1071
|
+
env.registry.register({
|
|
1072
|
+
name: registeredName,
|
|
1073
|
+
description: mcpTool.description ?? "",
|
|
1074
|
+
parameters: mcpTool.inputSchema,
|
|
1075
|
+
fn: async (args, ctx) => {
|
|
1076
|
+
const t0 = env.tracker ? Date.now() : 0;
|
|
1077
|
+
const live = env.host.client;
|
|
1078
|
+
const toolResult = await live.callTool(mcpTool.name, args, {
|
|
1079
|
+
onProgress: env.onProgress ? (info) => env.onProgress({ toolName: registeredName, ...info }) : void 0,
|
|
1080
|
+
signal: ctx?.signal
|
|
1081
|
+
});
|
|
1082
|
+
if (env.tracker) env.tracker.record(Date.now() - t0);
|
|
1083
|
+
return flattenMcpResult(toolResult, { maxChars: env.maxResultChars });
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
return registeredName;
|
|
1087
|
+
}
|
|
1035
1088
|
async function bridgeMcpTools(client, opts = {}) {
|
|
1036
1089
|
const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
|
|
1037
1090
|
const prefix = opts.namePrefix ?? "";
|
|
1038
1091
|
const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS;
|
|
1039
1092
|
const result = { registry, registeredNames: [], skipped: [] };
|
|
1093
|
+
const serverName = opts.serverName ?? prefix.replace(/_$/, "") ?? "anon";
|
|
1094
|
+
const tracker = opts.onSlow ? new LatencyTracker(serverName, { thresholdMs: opts.slowThresholdMs, onSlow: opts.onSlow }) : null;
|
|
1095
|
+
const host = opts.host ?? { client };
|
|
1096
|
+
const env = {
|
|
1097
|
+
registry,
|
|
1098
|
+
host,
|
|
1099
|
+
prefix,
|
|
1100
|
+
maxResultChars,
|
|
1101
|
+
tracker,
|
|
1102
|
+
onProgress: opts.onProgress
|
|
1103
|
+
};
|
|
1040
1104
|
const listed = await client.listTools();
|
|
1041
1105
|
for (const mcpTool of listed.tools) {
|
|
1042
1106
|
if (!mcpTool.name) {
|
|
1043
1107
|
result.skipped.push({ name: "?", reason: "empty tool name" });
|
|
1044
1108
|
continue;
|
|
1045
1109
|
}
|
|
1046
|
-
const registeredName =
|
|
1047
|
-
|
|
1048
|
-
name: registeredName,
|
|
1049
|
-
description: mcpTool.description ?? "",
|
|
1050
|
-
parameters: mcpTool.inputSchema,
|
|
1051
|
-
fn: async (args, ctx) => {
|
|
1052
|
-
const toolResult = await client.callTool(mcpTool.name, args, {
|
|
1053
|
-
// Forward server-side progress frames to the bridge caller,
|
|
1054
|
-
// tagged with the registered name so multi-server UIs can
|
|
1055
|
-
// disambiguate. No-op when `onProgress` isn't configured —
|
|
1056
|
-
// the client then also omits the _meta.progressToken and
|
|
1057
|
-
// the server won't emit progress.
|
|
1058
|
-
onProgress: opts.onProgress ? (info) => opts.onProgress({ toolName: registeredName, ...info }) : void 0,
|
|
1059
|
-
// Thread the tool-dispatch AbortSignal all the way down to
|
|
1060
|
-
// the MCP request so Esc truly cancels in flight — the
|
|
1061
|
-
// client will emit notifications/cancelled AND reject the
|
|
1062
|
-
// pending promise immediately, no "wait for subprocess".
|
|
1063
|
-
signal: ctx?.signal
|
|
1064
|
-
});
|
|
1065
|
-
return flattenMcpResult(toolResult, { maxChars: maxResultChars });
|
|
1066
|
-
}
|
|
1067
|
-
});
|
|
1068
|
-
result.registeredNames.push(registeredName);
|
|
1110
|
+
const registeredName = registerSingleMcpTool(mcpTool, env);
|
|
1111
|
+
if (registeredName) result.registeredNames.push(registeredName);
|
|
1069
1112
|
}
|
|
1070
|
-
return result;
|
|
1113
|
+
return { ...result, env };
|
|
1071
1114
|
}
|
|
1072
1115
|
function flattenMcpResult(result, opts = {}) {
|
|
1073
1116
|
const parts = result.content.map(blockToString);
|