reasonix 0.3.0-alpha.3 → 0.3.0-alpha.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/dist/index.js CHANGED
@@ -404,6 +404,201 @@ function resolveTemperatures(budget, custom) {
404
404
  return out;
405
405
  }
406
406
 
407
+ // src/repair/flatten.ts
408
+ function analyzeSchema(schema) {
409
+ if (!schema) return { shouldFlatten: false, leafCount: 0, maxDepth: 0 };
410
+ let leafCount = 0;
411
+ let maxDepth = 0;
412
+ walk(schema, 0, (depth, isLeaf) => {
413
+ if (isLeaf) leafCount++;
414
+ if (depth > maxDepth) maxDepth = depth;
415
+ });
416
+ return {
417
+ shouldFlatten: leafCount > 10 || maxDepth > 2,
418
+ leafCount,
419
+ maxDepth
420
+ };
421
+ }
422
+ function flattenSchema(schema) {
423
+ const flatProps = {};
424
+ const required = [];
425
+ collect("", schema, flatProps, required, true);
426
+ return {
427
+ type: "object",
428
+ properties: flatProps,
429
+ required
430
+ };
431
+ }
432
+ function nestArguments(flatArgs) {
433
+ const out = {};
434
+ for (const [key, value] of Object.entries(flatArgs)) {
435
+ setByPath(out, key.split("."), value);
436
+ }
437
+ return out;
438
+ }
439
+ function walk(schema, depth, visit) {
440
+ if (schema.type === "object" && schema.properties) {
441
+ for (const child of Object.values(schema.properties)) {
442
+ walk(child, depth + 1, visit);
443
+ }
444
+ return;
445
+ }
446
+ if (schema.type === "array" && schema.items) {
447
+ walk(schema.items, depth + 1, visit);
448
+ return;
449
+ }
450
+ visit(depth, true);
451
+ }
452
+ function collect(prefix, schema, out, required, isRootRequired) {
453
+ if (schema.type === "object" && schema.properties) {
454
+ const requiredSet = new Set(schema.required ?? []);
455
+ for (const [key, child] of Object.entries(schema.properties)) {
456
+ const nextPrefix = prefix ? `${prefix}.${key}` : key;
457
+ const childRequired = isRootRequired && requiredSet.has(key);
458
+ collect(nextPrefix, child, out, required, childRequired);
459
+ }
460
+ return;
461
+ }
462
+ out[prefix] = schema;
463
+ if (isRootRequired) required.push(prefix);
464
+ }
465
+ function setByPath(target, path, value) {
466
+ let cur = target;
467
+ for (let i = 0; i < path.length - 1; i++) {
468
+ const key = path[i];
469
+ if (typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
470
+ cur = cur[key];
471
+ }
472
+ cur[path[path.length - 1]] = value;
473
+ }
474
+
475
+ // src/tools.ts
476
+ var ToolRegistry = class {
477
+ _tools = /* @__PURE__ */ new Map();
478
+ _autoFlatten;
479
+ constructor(opts = {}) {
480
+ this._autoFlatten = opts.autoFlatten !== false;
481
+ }
482
+ register(def) {
483
+ if (!def.name) throw new Error("tool requires a name");
484
+ const internal = { ...def };
485
+ if (this._autoFlatten && def.parameters) {
486
+ const decision = analyzeSchema(def.parameters);
487
+ if (decision.shouldFlatten) {
488
+ internal.flatSchema = flattenSchema(def.parameters);
489
+ }
490
+ }
491
+ this._tools.set(def.name, internal);
492
+ return this;
493
+ }
494
+ has(name) {
495
+ return this._tools.has(name);
496
+ }
497
+ get(name) {
498
+ return this._tools.get(name);
499
+ }
500
+ get size() {
501
+ return this._tools.size;
502
+ }
503
+ /** True if a registered tool's schema was flattened for the model. */
504
+ wasFlattened(name) {
505
+ return Boolean(this._tools.get(name)?.flatSchema);
506
+ }
507
+ specs() {
508
+ return [...this._tools.values()].map((t) => ({
509
+ type: "function",
510
+ function: {
511
+ name: t.name,
512
+ description: t.description ?? "",
513
+ parameters: t.flatSchema ?? t.parameters ?? { type: "object", properties: {} }
514
+ }
515
+ }));
516
+ }
517
+ async dispatch(name, argumentsRaw) {
518
+ const tool = this._tools.get(name);
519
+ if (!tool) {
520
+ return JSON.stringify({ error: `unknown tool: ${name}` });
521
+ }
522
+ let args;
523
+ try {
524
+ args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) ?? {} : {} : argumentsRaw ?? {};
525
+ } catch (err) {
526
+ return JSON.stringify({
527
+ error: `invalid tool arguments JSON: ${err.message}`
528
+ });
529
+ }
530
+ if (tool.flatSchema && args && typeof args === "object" && hasDotKey(args)) {
531
+ args = nestArguments(args);
532
+ }
533
+ try {
534
+ const result = await tool.fn(args);
535
+ return typeof result === "string" ? result : JSON.stringify(result);
536
+ } catch (err) {
537
+ return JSON.stringify({
538
+ error: `${err.name}: ${err.message}`
539
+ });
540
+ }
541
+ }
542
+ };
543
+ function hasDotKey(obj) {
544
+ for (const k of Object.keys(obj)) {
545
+ if (k.includes(".")) return true;
546
+ }
547
+ return false;
548
+ }
549
+
550
+ // src/mcp/registry.ts
551
+ var DEFAULT_MAX_RESULT_CHARS = 32e3;
552
+ async function bridgeMcpTools(client, opts = {}) {
553
+ const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
554
+ const prefix = opts.namePrefix ?? "";
555
+ const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS;
556
+ const result = { registry, registeredNames: [], skipped: [] };
557
+ const listed = await client.listTools();
558
+ for (const mcpTool of listed.tools) {
559
+ if (!mcpTool.name) {
560
+ result.skipped.push({ name: "?", reason: "empty tool name" });
561
+ continue;
562
+ }
563
+ const registeredName = `${prefix}${mcpTool.name}`;
564
+ registry.register({
565
+ name: registeredName,
566
+ description: mcpTool.description ?? "",
567
+ parameters: mcpTool.inputSchema,
568
+ fn: async (args) => {
569
+ const toolResult = await client.callTool(mcpTool.name, args);
570
+ return flattenMcpResult(toolResult, { maxChars: maxResultChars });
571
+ }
572
+ });
573
+ result.registeredNames.push(registeredName);
574
+ }
575
+ return result;
576
+ }
577
+ function flattenMcpResult(result, opts = {}) {
578
+ const parts = result.content.map(blockToString);
579
+ const joined = parts.join("\n").trim();
580
+ const prefixed = result.isError ? `ERROR: ${joined || "(no error message from server)"}` : joined;
581
+ return opts.maxChars ? truncateForModel(prefixed, opts.maxChars) : prefixed;
582
+ }
583
+ function truncateForModel(s, maxChars) {
584
+ if (s.length <= maxChars) return s;
585
+ const tailBudget = Math.min(1024, Math.floor(maxChars * 0.1));
586
+ const headBudget = Math.max(0, maxChars - tailBudget);
587
+ const head = s.slice(0, headBudget);
588
+ const tail = s.slice(-tailBudget);
589
+ const dropped = s.length - head.length - tail.length;
590
+ return `${head}
591
+
592
+ [\u2026truncated ${dropped} chars \u2014 raise BridgeOptions.maxResultChars, or call the tool with a narrower scope (filter, head, pagination)\u2026]
593
+
594
+ ${tail}`;
595
+ }
596
+ function blockToString(block) {
597
+ if (block.type === "text") return block.text;
598
+ if (block.type === "image") return `[image ${block.mimeType}, ${block.data.length} chars base64]`;
599
+ return `[unknown block: ${JSON.stringify(block)}]`;
600
+ }
601
+
407
602
  // src/memory.ts
408
603
  import { createHash } from "crypto";
409
604
  var ImmutablePrefix = class {
@@ -655,74 +850,6 @@ function repairTruncatedJson(input) {
655
850
  }
656
851
  }
657
852
 
658
- // src/repair/flatten.ts
659
- function analyzeSchema(schema) {
660
- if (!schema) return { shouldFlatten: false, leafCount: 0, maxDepth: 0 };
661
- let leafCount = 0;
662
- let maxDepth = 0;
663
- walk(schema, 0, (depth, isLeaf) => {
664
- if (isLeaf) leafCount++;
665
- if (depth > maxDepth) maxDepth = depth;
666
- });
667
- return {
668
- shouldFlatten: leafCount > 10 || maxDepth > 2,
669
- leafCount,
670
- maxDepth
671
- };
672
- }
673
- function flattenSchema(schema) {
674
- const flatProps = {};
675
- const required = [];
676
- collect("", schema, flatProps, required, true);
677
- return {
678
- type: "object",
679
- properties: flatProps,
680
- required
681
- };
682
- }
683
- function nestArguments(flatArgs) {
684
- const out = {};
685
- for (const [key, value] of Object.entries(flatArgs)) {
686
- setByPath(out, key.split("."), value);
687
- }
688
- return out;
689
- }
690
- function walk(schema, depth, visit) {
691
- if (schema.type === "object" && schema.properties) {
692
- for (const child of Object.values(schema.properties)) {
693
- walk(child, depth + 1, visit);
694
- }
695
- return;
696
- }
697
- if (schema.type === "array" && schema.items) {
698
- walk(schema.items, depth + 1, visit);
699
- return;
700
- }
701
- visit(depth, true);
702
- }
703
- function collect(prefix, schema, out, required, isRootRequired) {
704
- if (schema.type === "object" && schema.properties) {
705
- const requiredSet = new Set(schema.required ?? []);
706
- for (const [key, child] of Object.entries(schema.properties)) {
707
- const nextPrefix = prefix ? `${prefix}.${key}` : key;
708
- const childRequired = isRootRequired && requiredSet.has(key);
709
- collect(nextPrefix, child, out, required, childRequired);
710
- }
711
- return;
712
- }
713
- out[prefix] = schema;
714
- if (isRootRequired) required.push(prefix);
715
- }
716
- function setByPath(target, path, value) {
717
- let cur = target;
718
- for (let i = 0; i < path.length - 1; i++) {
719
- const key = path[i];
720
- if (typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
721
- cur = cur[key];
722
- }
723
- cur[path[path.length - 1]] = value;
724
- }
725
-
726
853
  // src/repair/index.ts
727
854
  var ToolCallRepair = class {
728
855
  storm;
@@ -928,81 +1055,6 @@ function round(n, digits) {
928
1055
  return Math.round(n * f) / f;
929
1056
  }
930
1057
 
931
- // src/tools.ts
932
- var ToolRegistry = class {
933
- _tools = /* @__PURE__ */ new Map();
934
- _autoFlatten;
935
- constructor(opts = {}) {
936
- this._autoFlatten = opts.autoFlatten !== false;
937
- }
938
- register(def) {
939
- if (!def.name) throw new Error("tool requires a name");
940
- const internal = { ...def };
941
- if (this._autoFlatten && def.parameters) {
942
- const decision = analyzeSchema(def.parameters);
943
- if (decision.shouldFlatten) {
944
- internal.flatSchema = flattenSchema(def.parameters);
945
- }
946
- }
947
- this._tools.set(def.name, internal);
948
- return this;
949
- }
950
- has(name) {
951
- return this._tools.has(name);
952
- }
953
- get(name) {
954
- return this._tools.get(name);
955
- }
956
- get size() {
957
- return this._tools.size;
958
- }
959
- /** True if a registered tool's schema was flattened for the model. */
960
- wasFlattened(name) {
961
- return Boolean(this._tools.get(name)?.flatSchema);
962
- }
963
- specs() {
964
- return [...this._tools.values()].map((t) => ({
965
- type: "function",
966
- function: {
967
- name: t.name,
968
- description: t.description ?? "",
969
- parameters: t.flatSchema ?? t.parameters ?? { type: "object", properties: {} }
970
- }
971
- }));
972
- }
973
- async dispatch(name, argumentsRaw) {
974
- const tool = this._tools.get(name);
975
- if (!tool) {
976
- return JSON.stringify({ error: `unknown tool: ${name}` });
977
- }
978
- let args;
979
- try {
980
- args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) ?? {} : {} : argumentsRaw ?? {};
981
- } catch (err) {
982
- return JSON.stringify({
983
- error: `invalid tool arguments JSON: ${err.message}`
984
- });
985
- }
986
- if (tool.flatSchema && args && typeof args === "object" && hasDotKey(args)) {
987
- args = nestArguments(args);
988
- }
989
- try {
990
- const result = await tool.fn(args);
991
- return typeof result === "string" ? result : JSON.stringify(result);
992
- } catch (err) {
993
- return JSON.stringify({
994
- error: `${err.name}: ${err.message}`
995
- });
996
- }
997
- }
998
- };
999
- function hasDotKey(obj) {
1000
- for (const k of Object.keys(obj)) {
1001
- if (k.includes(".")) return true;
1002
- }
1003
- return false;
1004
- }
1005
-
1006
1058
  // src/loop.ts
1007
1059
  var CacheFirstLoop = class {
1008
1060
  client;
@@ -1050,8 +1102,18 @@ var CacheFirstLoop = class {
1050
1102
  this.sessionName = opts.session ?? null;
1051
1103
  if (this.sessionName) {
1052
1104
  const prior = loadSessionMessages(this.sessionName);
1053
- for (const msg of prior) this.log.append(msg);
1054
- this.resumedMessageCount = prior.length;
1105
+ const { messages, healedCount, healedFrom } = healLoadedMessages(
1106
+ prior,
1107
+ DEFAULT_MAX_RESULT_CHARS
1108
+ );
1109
+ for (const msg of messages) this.log.append(msg);
1110
+ this.resumedMessageCount = messages.length;
1111
+ if (healedCount > 0) {
1112
+ process.stderr.write(
1113
+ `\u25B8 session "${this.sessionName}": healed ${healedCount} oversized tool result(s) (was ${healedFrom.toLocaleString()} chars total). Old payloads were truncated to fit DeepSeek's context window; the conversation is preserved.
1114
+ `
1115
+ );
1116
+ }
1055
1117
  } else {
1056
1118
  this.resumedMessageCount = 0;
1057
1119
  }
@@ -1245,7 +1307,7 @@ var CacheFirstLoop = class {
1245
1307
  turn: this._turn,
1246
1308
  role: "error",
1247
1309
  content: "",
1248
- error: err.message
1310
+ error: formatLoopError(err)
1249
1311
  };
1250
1312
  return;
1251
1313
  }
@@ -1318,6 +1380,28 @@ function summarizeBranch(chosen, samples) {
1318
1380
  temperatures: samples.map((s) => s.temperature)
1319
1381
  };
1320
1382
  }
1383
+ function healLoadedMessages(messages, maxChars) {
1384
+ let healedCount = 0;
1385
+ let healedFrom = 0;
1386
+ const out = messages.map((msg) => {
1387
+ if (msg.role !== "tool") return msg;
1388
+ const content = typeof msg.content === "string" ? msg.content : "";
1389
+ if (content.length <= maxChars) return msg;
1390
+ healedCount += 1;
1391
+ healedFrom += content.length;
1392
+ return { ...msg, content: truncateForModel(content, maxChars) };
1393
+ });
1394
+ return { messages: out, healedCount, healedFrom };
1395
+ }
1396
+ function formatLoopError(err) {
1397
+ const msg = err.message ?? "";
1398
+ if (msg.includes("maximum context length")) {
1399
+ const reqMatch = msg.match(/requested\s+(\d+)\s+tokens/);
1400
+ const requested = reqMatch ? `${Number(reqMatch[1]).toLocaleString()} tokens` : "too many tokens";
1401
+ return `Context overflow (DeepSeek 400): session history is ${requested}, past the 131,072-token limit. Usually this means a single tool call returned a huge payload. v0.3.0-alpha.6+ caps new tool results at 32k chars, AND auto-heals oversized history on session load \u2014 restart Reasonix and this session should come back trimmed. If it still overflows, run /forget (delete the session) or /clear (drop the displayed history) to start fresh.`;
1402
+ }
1403
+ return msg;
1404
+ }
1321
1405
 
1322
1406
  // src/env.ts
1323
1407
  import { readFileSync as readFileSync2 } from "fs";
@@ -2067,43 +2151,217 @@ function quoteArg(s, windows) {
2067
2151
  return `"${s.replace(/"/g, '""')}"`;
2068
2152
  }
2069
2153
 
2070
- // src/mcp/registry.ts
2071
- async function bridgeMcpTools(client, opts = {}) {
2072
- const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
2073
- const prefix = opts.namePrefix ?? "";
2074
- const result = { registry, registeredNames: [], skipped: [] };
2075
- const listed = await client.listTools();
2076
- for (const mcpTool of listed.tools) {
2077
- if (!mcpTool.name) {
2078
- result.skipped.push({ name: "?", reason: "empty tool name" });
2079
- continue;
2154
+ // src/mcp/sse.ts
2155
+ import { createParser as createParser2 } from "eventsource-parser";
2156
+ var SseTransport = class {
2157
+ url;
2158
+ headers;
2159
+ queue = [];
2160
+ waiters = [];
2161
+ controller = new AbortController();
2162
+ closed = false;
2163
+ postUrl = null;
2164
+ endpointReady;
2165
+ resolveEndpoint;
2166
+ rejectEndpoint;
2167
+ constructor(opts) {
2168
+ this.url = opts.url;
2169
+ this.headers = opts.headers ?? {};
2170
+ this.endpointReady = new Promise((resolve2, reject) => {
2171
+ this.resolveEndpoint = resolve2;
2172
+ this.rejectEndpoint = reject;
2173
+ });
2174
+ this.endpointReady.catch(() => void 0);
2175
+ void this.runStream();
2176
+ }
2177
+ async send(message) {
2178
+ if (this.closed) throw new Error("MCP SSE transport is closed");
2179
+ const postUrl = await this.endpointReady;
2180
+ const res = await fetch(postUrl, {
2181
+ method: "POST",
2182
+ headers: { "content-type": "application/json", ...this.headers },
2183
+ body: JSON.stringify(message),
2184
+ signal: this.controller.signal
2185
+ });
2186
+ await res.arrayBuffer().catch(() => void 0);
2187
+ if (!res.ok) {
2188
+ throw new Error(`MCP SSE POST ${postUrl} failed: ${res.status} ${res.statusText}`);
2080
2189
  }
2081
- const registeredName = `${prefix}${mcpTool.name}`;
2082
- registry.register({
2083
- name: registeredName,
2084
- description: mcpTool.description ?? "",
2085
- parameters: mcpTool.inputSchema,
2086
- fn: async (args) => {
2087
- const toolResult = await client.callTool(mcpTool.name, args);
2088
- return flattenMcpResult(toolResult);
2190
+ }
2191
+ async *messages() {
2192
+ while (true) {
2193
+ if (this.queue.length > 0) {
2194
+ yield this.queue.shift();
2195
+ continue;
2196
+ }
2197
+ if (this.closed) return;
2198
+ const next = await new Promise((resolve2) => {
2199
+ this.waiters.push(resolve2);
2200
+ });
2201
+ if (next === null) return;
2202
+ yield next;
2203
+ }
2204
+ }
2205
+ async close() {
2206
+ if (this.closed) return;
2207
+ this.closed = true;
2208
+ while (this.waiters.length > 0) this.waiters.shift()(null);
2209
+ this.rejectEndpoint(new Error("MCP SSE transport closed before endpoint was ready"));
2210
+ try {
2211
+ this.controller.abort();
2212
+ } catch {
2213
+ }
2214
+ }
2215
+ // ---------- internals ----------
2216
+ async runStream() {
2217
+ let res;
2218
+ try {
2219
+ res = await fetch(this.url, {
2220
+ method: "GET",
2221
+ headers: { accept: "text/event-stream", ...this.headers },
2222
+ signal: this.controller.signal
2223
+ });
2224
+ } catch (err) {
2225
+ this.failHandshake(`SSE connect to ${this.url} failed: ${err.message}`);
2226
+ return;
2227
+ }
2228
+ if (!res.ok || !res.body) {
2229
+ await res.body?.cancel().catch(() => void 0);
2230
+ this.failHandshake(`SSE handshake ${this.url} \u2192 ${res.status} ${res.statusText}`);
2231
+ return;
2232
+ }
2233
+ const parser = createParser2({
2234
+ onEvent: (ev) => this.handleEvent(ev.event ?? "message", ev.data)
2235
+ });
2236
+ const decoder = new TextDecoder();
2237
+ try {
2238
+ for await (const chunk of res.body) {
2239
+ parser.feed(decoder.decode(chunk, { stream: true }));
2240
+ }
2241
+ } catch (err) {
2242
+ if (!this.closed) {
2243
+ this.pushError(`SSE stream error: ${err.message}`);
2089
2244
  }
2245
+ } finally {
2246
+ this.markClosed();
2247
+ }
2248
+ }
2249
+ handleEvent(type, data) {
2250
+ if (type === "endpoint") {
2251
+ if (this.postUrl) return;
2252
+ try {
2253
+ this.postUrl = new URL(data, this.url).toString();
2254
+ this.resolveEndpoint(this.postUrl);
2255
+ } catch (err) {
2256
+ this.failHandshake(`SSE endpoint event had bad URL "${data}": ${err.message}`);
2257
+ }
2258
+ return;
2259
+ }
2260
+ if (type === "message") {
2261
+ try {
2262
+ const parsed = JSON.parse(data);
2263
+ this.pushMessage(parsed);
2264
+ } catch {
2265
+ }
2266
+ return;
2267
+ }
2268
+ }
2269
+ failHandshake(reason) {
2270
+ this.rejectEndpoint(new Error(reason));
2271
+ this.pushError(reason);
2272
+ this.markClosed();
2273
+ }
2274
+ pushMessage(msg) {
2275
+ const waiter = this.waiters.shift();
2276
+ if (waiter) waiter(msg);
2277
+ else this.queue.push(msg);
2278
+ }
2279
+ pushError(message) {
2280
+ this.pushMessage({
2281
+ jsonrpc: "2.0",
2282
+ id: null,
2283
+ error: { code: -32e3, message }
2090
2284
  });
2091
- result.registeredNames.push(registeredName);
2092
2285
  }
2093
- return result;
2094
- }
2095
- function flattenMcpResult(result) {
2096
- const parts = result.content.map(blockToString);
2097
- const joined = parts.join("\n").trim();
2098
- if (result.isError) {
2099
- return `ERROR: ${joined || "(no error message from server)"}`;
2286
+ markClosed() {
2287
+ if (this.closed) return;
2288
+ this.closed = true;
2289
+ while (this.waiters.length > 0) this.waiters.shift()(null);
2100
2290
  }
2101
- return joined;
2291
+ };
2292
+
2293
+ // src/mcp/shell-split.ts
2294
+ function shellSplit(input) {
2295
+ const tokens = [];
2296
+ let cur = "";
2297
+ let quote = null;
2298
+ let i = 0;
2299
+ const s = input;
2300
+ while (i < s.length) {
2301
+ const ch = s[i];
2302
+ if (quote) {
2303
+ if (ch === quote) {
2304
+ quote = null;
2305
+ i++;
2306
+ continue;
2307
+ }
2308
+ if (ch === "\\" && quote === '"' && i + 1 < s.length) {
2309
+ cur += s[i + 1];
2310
+ i += 2;
2311
+ continue;
2312
+ }
2313
+ cur += ch;
2314
+ i++;
2315
+ continue;
2316
+ }
2317
+ if (ch === '"' || ch === "'") {
2318
+ quote = ch;
2319
+ i++;
2320
+ continue;
2321
+ }
2322
+ if (ch === " " || ch === " ") {
2323
+ if (cur.length > 0) {
2324
+ tokens.push(cur);
2325
+ cur = "";
2326
+ }
2327
+ i++;
2328
+ continue;
2329
+ }
2330
+ cur += ch;
2331
+ i++;
2332
+ }
2333
+ if (quote) {
2334
+ throw new Error(
2335
+ `shellSplit: unterminated ${quote === '"' ? "double" : "single"} quote in input`
2336
+ );
2337
+ }
2338
+ if (cur.length > 0) tokens.push(cur);
2339
+ return tokens;
2102
2340
  }
2103
- function blockToString(block) {
2104
- if (block.type === "text") return block.text;
2105
- if (block.type === "image") return `[image ${block.mimeType}, ${block.data.length} chars base64]`;
2106
- return `[unknown block: ${JSON.stringify(block)}]`;
2341
+
2342
+ // src/mcp/spec.ts
2343
+ var NAME_PREFIX = /^([a-zA-Z_][a-zA-Z0-9_]*)=(.*)$/;
2344
+ var HTTP_URL = /^https?:\/\//i;
2345
+ function parseMcpSpec(input) {
2346
+ const trimmed = input.trim();
2347
+ if (!trimmed) {
2348
+ throw new Error("empty MCP spec");
2349
+ }
2350
+ const nameMatch = NAME_PREFIX.exec(trimmed);
2351
+ const name = nameMatch ? nameMatch[1] : null;
2352
+ const body = (nameMatch ? nameMatch[2] : trimmed).trim();
2353
+ if (!body) {
2354
+ throw new Error(`MCP spec has name but no command: ${input}`);
2355
+ }
2356
+ if (HTTP_URL.test(body)) {
2357
+ return { transport: "sse", name, url: body };
2358
+ }
2359
+ const argv = shellSplit(body);
2360
+ if (argv.length === 0) {
2361
+ throw new Error(`MCP spec has name but no command: ${input}`);
2362
+ }
2363
+ const [command, ...args] = argv;
2364
+ return { transport: "stdio", name, command, args };
2107
2365
  }
2108
2366
 
2109
2367
  // src/config.ts
@@ -2150,15 +2408,17 @@ function redactKey(key) {
2150
2408
  }
2151
2409
 
2152
2410
  // src/index.ts
2153
- var VERSION = "0.3.0-alpha.3";
2411
+ var VERSION = "0.3.0-alpha.6";
2154
2412
  export {
2155
2413
  AppendOnlyLog,
2156
2414
  CacheFirstLoop,
2415
+ DEFAULT_MAX_RESULT_CHARS,
2157
2416
  DeepSeekClient,
2158
2417
  ImmutablePrefix,
2159
2418
  MCP_PROTOCOL_VERSION,
2160
2419
  McpClient,
2161
2420
  SessionStats,
2421
+ SseTransport,
2162
2422
  StdioTransport,
2163
2423
  StormBreaker,
2164
2424
  ToolCallRepair,
@@ -2181,7 +2441,9 @@ export {
2181
2441
  fetchWithRetry,
2182
2442
  flattenMcpResult,
2183
2443
  flattenSchema,
2444
+ formatLoopError,
2184
2445
  harvest,
2446
+ healLoadedMessages,
2185
2447
  isJsonRpcError,
2186
2448
  isPlanStateEmpty,
2187
2449
  isPlausibleKey,
@@ -2191,6 +2453,7 @@ export {
2191
2453
  loadSessionMessages,
2192
2454
  nestArguments,
2193
2455
  openTranscriptFile,
2456
+ parseMcpSpec,
2194
2457
  parseTranscript,
2195
2458
  readConfig,
2196
2459
  readTranscript,
@@ -2207,6 +2470,7 @@ export {
2207
2470
  sessionPath,
2208
2471
  sessionsDir,
2209
2472
  similarity,
2473
+ truncateForModel,
2210
2474
  writeConfig,
2211
2475
  writeMeta,
2212
2476
  writeRecord