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/README.md +5 -0
- package/dist/cli/index.js +1011 -320
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +177 -3
- package/dist/index.js +442 -178
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
1054
|
-
|
|
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
|
|
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/
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
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
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
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
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
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.
|
|
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
|