reasonix 0.3.0-alpha.3 → 0.3.0-alpha.4

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
@@ -994,6 +994,61 @@ declare class McpClient {
994
994
  private dispatch;
995
995
  }
996
996
 
997
+ /**
998
+ * HTTP+SSE transport for MCP (spec version 2024-11-05).
999
+ *
1000
+ * Wire shape:
1001
+ * 1. Client opens GET to the SSE URL with `Accept: text/event-stream`.
1002
+ * 2. Server's first SSE event is `event: endpoint`, `data: <url>` — the
1003
+ * URL (relative or absolute) the client must POST JSON-RPC requests
1004
+ * to. All subsequent server → client messages arrive as `event: message`
1005
+ * SSE frames carrying a JSON-RPC response or server-initiated frame.
1006
+ * 3. Client POSTs each outgoing JSON-RPC frame to the endpoint URL.
1007
+ * The POST response body is ignored — replies land on the SSE stream.
1008
+ *
1009
+ * This transport exists so Reasonix can talk to hosted/remote MCP servers
1010
+ * (e.g. a company's internal knowledge server fronted by auth). Stdio
1011
+ * covers local subprocesses; SSE covers everything else.
1012
+ *
1013
+ * Note: the newer "Streamable HTTP" transport (2025 spec) folds the POST
1014
+ * and SSE streams onto a single endpoint. We stay on 2024-11-05 here —
1015
+ * that's what `MCP_PROTOCOL_VERSION` advertises in the initialize handshake
1016
+ * and what currently-published servers implement.
1017
+ */
1018
+
1019
+ interface SseTransportOptions {
1020
+ /** SSE endpoint URL, e.g. `https://mcp.example.com/sse`. */
1021
+ url: string;
1022
+ /** Extra headers sent on both the SSE GET and the JSON-RPC POSTs (e.g. `Authorization`). */
1023
+ headers?: Record<string, string>;
1024
+ }
1025
+ /**
1026
+ * Open an SSE stream to `url`, parse incoming events into JsonRpcMessages,
1027
+ * POST outgoing frames to the endpoint URL the server advertises.
1028
+ */
1029
+ declare class SseTransport implements McpTransport {
1030
+ private readonly url;
1031
+ private readonly headers;
1032
+ private readonly queue;
1033
+ private readonly waiters;
1034
+ private readonly controller;
1035
+ private closed;
1036
+ private postUrl;
1037
+ private readonly endpointReady;
1038
+ private resolveEndpoint;
1039
+ private rejectEndpoint;
1040
+ constructor(opts: SseTransportOptions);
1041
+ send(message: JsonRpcMessage): Promise<void>;
1042
+ messages(): AsyncIterableIterator<JsonRpcMessage>;
1043
+ close(): Promise<void>;
1044
+ private runStream;
1045
+ private handleEvent;
1046
+ private failHandshake;
1047
+ private pushMessage;
1048
+ private pushError;
1049
+ private markClosed;
1050
+ }
1051
+
997
1052
  /**
998
1053
  * Bridge: register an MCP server's tools into a Reasonix ToolRegistry.
999
1054
  *
@@ -1045,6 +1100,45 @@ declare function bridgeMcpTools(client: McpClient, opts?: BridgeOptions): Promis
1045
1100
  */
1046
1101
  declare function flattenMcpResult(result: CallToolResult): string;
1047
1102
 
1103
+ /**
1104
+ * Parse the `--mcp` CLI argument into a transport-tagged spec.
1105
+ *
1106
+ * Accepted forms:
1107
+ * "name=command args..." → stdio, namespaced (tools prefixed with `name_`)
1108
+ * "command args..." → stdio, anonymous
1109
+ * "name=https://host/sse" → SSE, namespaced
1110
+ * "https://host/sse" → SSE, anonymous
1111
+ * ("http://" is also honored — useful for local dev servers.)
1112
+ *
1113
+ * The identifier regex before `=` is deliberately narrow
1114
+ * (`[a-zA-Z_][a-zA-Z0-9_]*`) so Windows drive letters ("C:\\...") and
1115
+ * other strings containing `=` or `:` don't accidentally trigger the
1116
+ * namespace branch. If a user ever wants their command to literally start
1117
+ * with `foo=...` as a bare command, they can wrap it in quotes inside the
1118
+ * shell command string.
1119
+ *
1120
+ * Transport is selected solely by whether the body begins with `http://`
1121
+ * or `https://`. Anything else is stdio — including ws:// (unsupported)
1122
+ * which will surface later as a spawn error, keeping the rule local.
1123
+ */
1124
+ interface StdioMcpSpec {
1125
+ transport: "stdio";
1126
+ /** Namespace prefix applied to each registered tool, or null if anonymous. */
1127
+ name: string | null;
1128
+ /** Argv[0]. */
1129
+ command: string;
1130
+ /** Remaining argv. */
1131
+ args: string[];
1132
+ }
1133
+ interface SseMcpSpec {
1134
+ transport: "sse";
1135
+ name: string | null;
1136
+ /** Fully qualified SSE endpoint URL. */
1137
+ url: string;
1138
+ }
1139
+ type McpSpec = StdioMcpSpec | SseMcpSpec;
1140
+ declare function parseMcpSpec(input: string): McpSpec;
1141
+
1048
1142
  /**
1049
1143
  * User-level config storage for the Reasonix CLI.
1050
1144
  *
@@ -1072,6 +1166,6 @@ declare function redactKey(key: string): string;
1072
1166
 
1073
1167
  /** Reasonix — DeepSeek-native agent framework. Library entry point. */
1074
1168
 
1075
- declare const VERSION = "0.3.0-alpha.3";
1169
+ declare const VERSION = "0.3.0-alpha.4";
1076
1170
 
1077
- export { AppendOnlyLog, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EventRole, type FlattenDecision, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, type ListToolsResult, type LoopEvent, MCP_PROTOCOL_VERSION, McpClient, type McpClientOptions, type McpContentBlock, type McpTool, type McpToolSchema, type McpTransport, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type RetryInfo, type RetryOptions, type Role, type ScavengeOptions, type ScavengeResult, type SessionInfo, SessionStats, type SessionSummary, StdioTransport, type StdioTransportOptions, StormBreaker, type StreamChunk, type ToolCall, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, Usage, VERSION, VolatileScratch, aggregateBranchUsage, analyzeSchema, appendSessionMessage, bridgeMcpTools, claudeEquivalentCost, computeReplayStats, costUsd, defaultConfigPath, defaultSelector, deleteSession, diffTranscripts, emptyPlanState, fetchWithRetry, flattenMcpResult, flattenSchema, harvest, isJsonRpcError, isPlanStateEmpty, isPlausibleKey, listSessions, loadApiKey, loadDotenv, loadSessionMessages, nestArguments, openTranscriptFile, parseTranscript, readConfig, readTranscript, recordFromLoopEvent, redactKey, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, runBranches, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, writeConfig, writeMeta, writeRecord };
1171
+ export { AppendOnlyLog, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EventRole, type FlattenDecision, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, type ListToolsResult, type LoopEvent, MCP_PROTOCOL_VERSION, McpClient, type McpClientOptions, type McpContentBlock, type McpSpec, type McpTool, type McpToolSchema, type McpTransport, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type RetryInfo, type RetryOptions, type Role, type ScavengeOptions, type ScavengeResult, type SessionInfo, SessionStats, type SessionSummary, type SseMcpSpec, SseTransport, type SseTransportOptions, type StdioMcpSpec, StdioTransport, type StdioTransportOptions, StormBreaker, type StreamChunk, type ToolCall, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, Usage, VERSION, VolatileScratch, aggregateBranchUsage, analyzeSchema, appendSessionMessage, bridgeMcpTools, claudeEquivalentCost, computeReplayStats, costUsd, defaultConfigPath, defaultSelector, deleteSession, diffTranscripts, emptyPlanState, fetchWithRetry, flattenMcpResult, flattenSchema, harvest, isJsonRpcError, isPlanStateEmpty, isPlausibleKey, listSessions, loadApiKey, loadDotenv, loadSessionMessages, nestArguments, openTranscriptFile, parseMcpSpec, parseTranscript, readConfig, readTranscript, recordFromLoopEvent, redactKey, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, runBranches, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, writeConfig, writeMeta, writeRecord };
package/dist/index.js CHANGED
@@ -2067,6 +2067,145 @@ function quoteArg(s, windows) {
2067
2067
  return `"${s.replace(/"/g, '""')}"`;
2068
2068
  }
2069
2069
 
2070
+ // src/mcp/sse.ts
2071
+ import { createParser as createParser2 } from "eventsource-parser";
2072
+ var SseTransport = class {
2073
+ url;
2074
+ headers;
2075
+ queue = [];
2076
+ waiters = [];
2077
+ controller = new AbortController();
2078
+ closed = false;
2079
+ postUrl = null;
2080
+ endpointReady;
2081
+ resolveEndpoint;
2082
+ rejectEndpoint;
2083
+ constructor(opts) {
2084
+ this.url = opts.url;
2085
+ this.headers = opts.headers ?? {};
2086
+ this.endpointReady = new Promise((resolve2, reject) => {
2087
+ this.resolveEndpoint = resolve2;
2088
+ this.rejectEndpoint = reject;
2089
+ });
2090
+ this.endpointReady.catch(() => void 0);
2091
+ void this.runStream();
2092
+ }
2093
+ async send(message) {
2094
+ if (this.closed) throw new Error("MCP SSE transport is closed");
2095
+ const postUrl = await this.endpointReady;
2096
+ const res = await fetch(postUrl, {
2097
+ method: "POST",
2098
+ headers: { "content-type": "application/json", ...this.headers },
2099
+ body: JSON.stringify(message),
2100
+ signal: this.controller.signal
2101
+ });
2102
+ await res.arrayBuffer().catch(() => void 0);
2103
+ if (!res.ok) {
2104
+ throw new Error(`MCP SSE POST ${postUrl} failed: ${res.status} ${res.statusText}`);
2105
+ }
2106
+ }
2107
+ async *messages() {
2108
+ while (true) {
2109
+ if (this.queue.length > 0) {
2110
+ yield this.queue.shift();
2111
+ continue;
2112
+ }
2113
+ if (this.closed) return;
2114
+ const next = await new Promise((resolve2) => {
2115
+ this.waiters.push(resolve2);
2116
+ });
2117
+ if (next === null) return;
2118
+ yield next;
2119
+ }
2120
+ }
2121
+ async close() {
2122
+ if (this.closed) return;
2123
+ this.closed = true;
2124
+ while (this.waiters.length > 0) this.waiters.shift()(null);
2125
+ this.rejectEndpoint(new Error("MCP SSE transport closed before endpoint was ready"));
2126
+ try {
2127
+ this.controller.abort();
2128
+ } catch {
2129
+ }
2130
+ }
2131
+ // ---------- internals ----------
2132
+ async runStream() {
2133
+ let res;
2134
+ try {
2135
+ res = await fetch(this.url, {
2136
+ method: "GET",
2137
+ headers: { accept: "text/event-stream", ...this.headers },
2138
+ signal: this.controller.signal
2139
+ });
2140
+ } catch (err) {
2141
+ this.failHandshake(`SSE connect to ${this.url} failed: ${err.message}`);
2142
+ return;
2143
+ }
2144
+ if (!res.ok || !res.body) {
2145
+ await res.body?.cancel().catch(() => void 0);
2146
+ this.failHandshake(`SSE handshake ${this.url} \u2192 ${res.status} ${res.statusText}`);
2147
+ return;
2148
+ }
2149
+ const parser = createParser2({
2150
+ onEvent: (ev) => this.handleEvent(ev.event ?? "message", ev.data)
2151
+ });
2152
+ const decoder = new TextDecoder();
2153
+ try {
2154
+ for await (const chunk of res.body) {
2155
+ parser.feed(decoder.decode(chunk, { stream: true }));
2156
+ }
2157
+ } catch (err) {
2158
+ if (!this.closed) {
2159
+ this.pushError(`SSE stream error: ${err.message}`);
2160
+ }
2161
+ } finally {
2162
+ this.markClosed();
2163
+ }
2164
+ }
2165
+ handleEvent(type, data) {
2166
+ if (type === "endpoint") {
2167
+ if (this.postUrl) return;
2168
+ try {
2169
+ this.postUrl = new URL(data, this.url).toString();
2170
+ this.resolveEndpoint(this.postUrl);
2171
+ } catch (err) {
2172
+ this.failHandshake(`SSE endpoint event had bad URL "${data}": ${err.message}`);
2173
+ }
2174
+ return;
2175
+ }
2176
+ if (type === "message") {
2177
+ try {
2178
+ const parsed = JSON.parse(data);
2179
+ this.pushMessage(parsed);
2180
+ } catch {
2181
+ }
2182
+ return;
2183
+ }
2184
+ }
2185
+ failHandshake(reason) {
2186
+ this.rejectEndpoint(new Error(reason));
2187
+ this.pushError(reason);
2188
+ this.markClosed();
2189
+ }
2190
+ pushMessage(msg) {
2191
+ const waiter = this.waiters.shift();
2192
+ if (waiter) waiter(msg);
2193
+ else this.queue.push(msg);
2194
+ }
2195
+ pushError(message) {
2196
+ this.pushMessage({
2197
+ jsonrpc: "2.0",
2198
+ id: null,
2199
+ error: { code: -32e3, message }
2200
+ });
2201
+ }
2202
+ markClosed() {
2203
+ if (this.closed) return;
2204
+ this.closed = true;
2205
+ while (this.waiters.length > 0) this.waiters.shift()(null);
2206
+ }
2207
+ };
2208
+
2070
2209
  // src/mcp/registry.ts
2071
2210
  async function bridgeMcpTools(client, opts = {}) {
2072
2211
  const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
@@ -2106,6 +2245,80 @@ function blockToString(block) {
2106
2245
  return `[unknown block: ${JSON.stringify(block)}]`;
2107
2246
  }
2108
2247
 
2248
+ // src/mcp/shell-split.ts
2249
+ function shellSplit(input) {
2250
+ const tokens = [];
2251
+ let cur = "";
2252
+ let quote = null;
2253
+ let i = 0;
2254
+ const s = input;
2255
+ while (i < s.length) {
2256
+ const ch = s[i];
2257
+ if (quote) {
2258
+ if (ch === quote) {
2259
+ quote = null;
2260
+ i++;
2261
+ continue;
2262
+ }
2263
+ if (ch === "\\" && quote === '"' && i + 1 < s.length) {
2264
+ cur += s[i + 1];
2265
+ i += 2;
2266
+ continue;
2267
+ }
2268
+ cur += ch;
2269
+ i++;
2270
+ continue;
2271
+ }
2272
+ if (ch === '"' || ch === "'") {
2273
+ quote = ch;
2274
+ i++;
2275
+ continue;
2276
+ }
2277
+ if (ch === " " || ch === " ") {
2278
+ if (cur.length > 0) {
2279
+ tokens.push(cur);
2280
+ cur = "";
2281
+ }
2282
+ i++;
2283
+ continue;
2284
+ }
2285
+ cur += ch;
2286
+ i++;
2287
+ }
2288
+ if (quote) {
2289
+ throw new Error(
2290
+ `shellSplit: unterminated ${quote === '"' ? "double" : "single"} quote in input`
2291
+ );
2292
+ }
2293
+ if (cur.length > 0) tokens.push(cur);
2294
+ return tokens;
2295
+ }
2296
+
2297
+ // src/mcp/spec.ts
2298
+ var NAME_PREFIX = /^([a-zA-Z_][a-zA-Z0-9_]*)=(.*)$/;
2299
+ var HTTP_URL = /^https?:\/\//i;
2300
+ function parseMcpSpec(input) {
2301
+ const trimmed = input.trim();
2302
+ if (!trimmed) {
2303
+ throw new Error("empty MCP spec");
2304
+ }
2305
+ const nameMatch = NAME_PREFIX.exec(trimmed);
2306
+ const name = nameMatch ? nameMatch[1] : null;
2307
+ const body = (nameMatch ? nameMatch[2] : trimmed).trim();
2308
+ if (!body) {
2309
+ throw new Error(`MCP spec has name but no command: ${input}`);
2310
+ }
2311
+ if (HTTP_URL.test(body)) {
2312
+ return { transport: "sse", name, url: body };
2313
+ }
2314
+ const argv = shellSplit(body);
2315
+ if (argv.length === 0) {
2316
+ throw new Error(`MCP spec has name but no command: ${input}`);
2317
+ }
2318
+ const [command, ...args] = argv;
2319
+ return { transport: "stdio", name, command, args };
2320
+ }
2321
+
2109
2322
  // src/config.ts
2110
2323
  import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "fs";
2111
2324
  import { homedir as homedir2 } from "os";
@@ -2150,7 +2363,7 @@ function redactKey(key) {
2150
2363
  }
2151
2364
 
2152
2365
  // src/index.ts
2153
- var VERSION = "0.3.0-alpha.3";
2366
+ var VERSION = "0.3.0-alpha.4";
2154
2367
  export {
2155
2368
  AppendOnlyLog,
2156
2369
  CacheFirstLoop,
@@ -2159,6 +2372,7 @@ export {
2159
2372
  MCP_PROTOCOL_VERSION,
2160
2373
  McpClient,
2161
2374
  SessionStats,
2375
+ SseTransport,
2162
2376
  StdioTransport,
2163
2377
  StormBreaker,
2164
2378
  ToolCallRepair,
@@ -2191,6 +2405,7 @@ export {
2191
2405
  loadSessionMessages,
2192
2406
  nestArguments,
2193
2407
  openTranscriptFile,
2408
+ parseMcpSpec,
2194
2409
  parseTranscript,
2195
2410
  readConfig,
2196
2411
  readTranscript,