reasonix 0.5.0 → 0.5.3
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 +366 -226
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +60 -5
- package/dist/index.js +390 -87
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -617,6 +617,170 @@ async function runHooks(opts) {
|
|
|
617
617
|
return { event, outcomes, blocked };
|
|
618
618
|
}
|
|
619
619
|
|
|
620
|
+
// src/tokenizer.ts
|
|
621
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
622
|
+
import { createRequire } from "module";
|
|
623
|
+
import { dirname, join as join2 } from "path";
|
|
624
|
+
import { fileURLToPath } from "url";
|
|
625
|
+
import { gunzipSync } from "zlib";
|
|
626
|
+
function buildByteToChar() {
|
|
627
|
+
const result = new Array(256);
|
|
628
|
+
const bs = [];
|
|
629
|
+
for (let b = 33; b <= 126; b++) bs.push(b);
|
|
630
|
+
for (let b = 161; b <= 172; b++) bs.push(b);
|
|
631
|
+
for (let b = 174; b <= 255; b++) bs.push(b);
|
|
632
|
+
const cs = bs.slice();
|
|
633
|
+
let n = 0;
|
|
634
|
+
for (let b = 0; b < 256; b++) {
|
|
635
|
+
if (!bs.includes(b)) {
|
|
636
|
+
bs.push(b);
|
|
637
|
+
cs.push(256 + n);
|
|
638
|
+
n++;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
for (let i = 0; i < bs.length; i++) {
|
|
642
|
+
result[bs[i]] = String.fromCodePoint(cs[i]);
|
|
643
|
+
}
|
|
644
|
+
return result;
|
|
645
|
+
}
|
|
646
|
+
var cached = null;
|
|
647
|
+
function resolveDataPath() {
|
|
648
|
+
if (process.env.REASONIX_TOKENIZER_PATH) return process.env.REASONIX_TOKENIZER_PATH;
|
|
649
|
+
try {
|
|
650
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
651
|
+
return join2(here, "..", "data", "deepseek-tokenizer.json.gz");
|
|
652
|
+
} catch {
|
|
653
|
+
const req = createRequire(import.meta.url);
|
|
654
|
+
return join2(
|
|
655
|
+
dirname(req.resolve("reasonix/package.json")),
|
|
656
|
+
"data",
|
|
657
|
+
"deepseek-tokenizer.json.gz"
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function loadTokenizer() {
|
|
662
|
+
if (cached) return cached;
|
|
663
|
+
const buf = readFileSync2(resolveDataPath());
|
|
664
|
+
const json = gunzipSync(buf).toString("utf8");
|
|
665
|
+
const data = JSON.parse(json);
|
|
666
|
+
const mergeRank = /* @__PURE__ */ new Map();
|
|
667
|
+
for (let i = 0; i < data.model.merges.length; i++) {
|
|
668
|
+
mergeRank.set(data.model.merges[i], i);
|
|
669
|
+
}
|
|
670
|
+
const splitRegexes = [];
|
|
671
|
+
for (const p of data.pre_tokenizer.pretokenizers) {
|
|
672
|
+
if (p.type === "Split") {
|
|
673
|
+
splitRegexes.push(new RegExp(p.pattern.Regex, "gu"));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
const addedMap = /* @__PURE__ */ new Map();
|
|
677
|
+
const addedContents = [];
|
|
678
|
+
for (const t of data.added_tokens) {
|
|
679
|
+
if (!t.special) {
|
|
680
|
+
addedMap.set(t.content, t.id);
|
|
681
|
+
addedContents.push(t.content);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
addedContents.sort((a, b) => b.length - a.length);
|
|
685
|
+
const addedPattern = addedContents.length ? new RegExp(addedContents.map(escapeRegex).join("|"), "g") : null;
|
|
686
|
+
cached = {
|
|
687
|
+
vocab: data.model.vocab,
|
|
688
|
+
mergeRank,
|
|
689
|
+
splitRegexes,
|
|
690
|
+
byteToChar: buildByteToChar(),
|
|
691
|
+
addedPattern,
|
|
692
|
+
addedMap
|
|
693
|
+
};
|
|
694
|
+
return cached;
|
|
695
|
+
}
|
|
696
|
+
function escapeRegex(s) {
|
|
697
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
698
|
+
}
|
|
699
|
+
function applySplit(chunks, re) {
|
|
700
|
+
const out = [];
|
|
701
|
+
for (const chunk of chunks) {
|
|
702
|
+
if (!chunk) continue;
|
|
703
|
+
re.lastIndex = 0;
|
|
704
|
+
let last = 0;
|
|
705
|
+
for (const m of chunk.matchAll(re)) {
|
|
706
|
+
const idx = m.index ?? 0;
|
|
707
|
+
if (idx > last) out.push(chunk.slice(last, idx));
|
|
708
|
+
if (m[0].length > 0) out.push(m[0]);
|
|
709
|
+
last = idx + m[0].length;
|
|
710
|
+
}
|
|
711
|
+
if (last < chunk.length) out.push(chunk.slice(last));
|
|
712
|
+
}
|
|
713
|
+
return out;
|
|
714
|
+
}
|
|
715
|
+
function byteLevelEncode(s, byteToChar) {
|
|
716
|
+
const bytes = new TextEncoder().encode(s);
|
|
717
|
+
let out = "";
|
|
718
|
+
for (let i = 0; i < bytes.length; i++) out += byteToChar[bytes[i]];
|
|
719
|
+
return out;
|
|
720
|
+
}
|
|
721
|
+
function bpeEncode(piece, mergeRank) {
|
|
722
|
+
if (piece.length <= 1) return piece ? [piece] : [];
|
|
723
|
+
let word = Array.from(piece);
|
|
724
|
+
while (true) {
|
|
725
|
+
let bestIdx = -1;
|
|
726
|
+
let bestRank = Number.POSITIVE_INFINITY;
|
|
727
|
+
for (let i = 0; i < word.length - 1; i++) {
|
|
728
|
+
const pair = `${word[i]} ${word[i + 1]}`;
|
|
729
|
+
const rank = mergeRank.get(pair);
|
|
730
|
+
if (rank !== void 0 && rank < bestRank) {
|
|
731
|
+
bestRank = rank;
|
|
732
|
+
bestIdx = i;
|
|
733
|
+
if (rank === 0) break;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (bestIdx < 0) break;
|
|
737
|
+
word = [
|
|
738
|
+
...word.slice(0, bestIdx),
|
|
739
|
+
word[bestIdx] + word[bestIdx + 1],
|
|
740
|
+
...word.slice(bestIdx + 2)
|
|
741
|
+
];
|
|
742
|
+
if (word.length === 1) break;
|
|
743
|
+
}
|
|
744
|
+
return word;
|
|
745
|
+
}
|
|
746
|
+
function encode(text) {
|
|
747
|
+
if (!text) return [];
|
|
748
|
+
const t = loadTokenizer();
|
|
749
|
+
const ids = [];
|
|
750
|
+
const process2 = (segment) => {
|
|
751
|
+
if (!segment) return;
|
|
752
|
+
let chunks = [segment];
|
|
753
|
+
for (const re of t.splitRegexes) chunks = applySplit(chunks, re);
|
|
754
|
+
for (const chunk of chunks) {
|
|
755
|
+
if (!chunk) continue;
|
|
756
|
+
const byteLevel = byteLevelEncode(chunk, t.byteToChar);
|
|
757
|
+
const pieces = bpeEncode(byteLevel, t.mergeRank);
|
|
758
|
+
for (const p of pieces) {
|
|
759
|
+
const id = t.vocab[p];
|
|
760
|
+
if (id !== void 0) ids.push(id);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
if (t.addedPattern) {
|
|
765
|
+
t.addedPattern.lastIndex = 0;
|
|
766
|
+
let last = 0;
|
|
767
|
+
for (const m of text.matchAll(t.addedPattern)) {
|
|
768
|
+
const idx = m.index ?? 0;
|
|
769
|
+
if (idx > last) process2(text.slice(last, idx));
|
|
770
|
+
const id = t.addedMap.get(m[0]);
|
|
771
|
+
if (id !== void 0) ids.push(id);
|
|
772
|
+
last = idx + m[0].length;
|
|
773
|
+
}
|
|
774
|
+
if (last < text.length) process2(text.slice(last));
|
|
775
|
+
} else {
|
|
776
|
+
process2(text);
|
|
777
|
+
}
|
|
778
|
+
return ids;
|
|
779
|
+
}
|
|
780
|
+
function countTokens(text) {
|
|
781
|
+
return encode(text).length;
|
|
782
|
+
}
|
|
783
|
+
|
|
620
784
|
// src/repair/flatten.ts
|
|
621
785
|
function analyzeSchema(schema) {
|
|
622
786
|
if (!schema) return { shouldFlatten: false, leafCount: 0, maxDepth: 0 };
|
|
@@ -767,7 +931,14 @@ var ToolRegistry = class {
|
|
|
767
931
|
try {
|
|
768
932
|
const result = await tool.fn(args, { signal: opts.signal });
|
|
769
933
|
const str = typeof result === "string" ? result : JSON.stringify(result);
|
|
770
|
-
|
|
934
|
+
let clipped = str;
|
|
935
|
+
if (opts.maxResultTokens !== void 0) {
|
|
936
|
+
clipped = truncateForModelByTokens(clipped, opts.maxResultTokens);
|
|
937
|
+
}
|
|
938
|
+
if (opts.maxResultChars !== void 0) {
|
|
939
|
+
clipped = truncateForModel(clipped, opts.maxResultChars);
|
|
940
|
+
}
|
|
941
|
+
return clipped;
|
|
771
942
|
} catch (err) {
|
|
772
943
|
const e = err;
|
|
773
944
|
if (typeof e.toToolResult === "function") {
|
|
@@ -801,6 +972,7 @@ function hasDotKey(obj) {
|
|
|
801
972
|
|
|
802
973
|
// src/mcp/registry.ts
|
|
803
974
|
var DEFAULT_MAX_RESULT_CHARS = 32e3;
|
|
975
|
+
var DEFAULT_MAX_RESULT_TOKENS = 8e3;
|
|
804
976
|
async function bridgeMcpTools(client, opts = {}) {
|
|
805
977
|
const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
|
|
806
978
|
const prefix = opts.namePrefix ?? "";
|
|
@@ -857,6 +1029,61 @@ function truncateForModel(s, maxChars) {
|
|
|
857
1029
|
|
|
858
1030
|
${tail}`;
|
|
859
1031
|
}
|
|
1032
|
+
function truncateForModelByTokens(s, maxTokens) {
|
|
1033
|
+
if (maxTokens <= 0) return "";
|
|
1034
|
+
if (s.length <= maxTokens) return s;
|
|
1035
|
+
if (s.length <= maxTokens * 4) {
|
|
1036
|
+
const tokens = countTokens(s);
|
|
1037
|
+
if (tokens <= maxTokens) return s;
|
|
1038
|
+
}
|
|
1039
|
+
const markerOverhead = 48;
|
|
1040
|
+
const contentBudget = Math.max(0, maxTokens - markerOverhead);
|
|
1041
|
+
const tailBudget = Math.min(256, Math.floor(contentBudget * 0.1));
|
|
1042
|
+
const headBudget = Math.max(0, contentBudget - tailBudget);
|
|
1043
|
+
const head = sizePrefixToTokens(s, headBudget);
|
|
1044
|
+
const tail = sizeSuffixToTokens(s, tailBudget);
|
|
1045
|
+
const droppedChars = s.length - head.length - tail.length;
|
|
1046
|
+
const headTokens = head ? countTokens(head) : 0;
|
|
1047
|
+
const tailTokens = tail ? countTokens(tail) : 0;
|
|
1048
|
+
const sampleChars = head.length + tail.length;
|
|
1049
|
+
const sampleTokens = headTokens + tailTokens;
|
|
1050
|
+
const ratio = sampleChars > 0 ? sampleTokens / sampleChars : 0.3;
|
|
1051
|
+
const estTotalTokens = Math.ceil(s.length * ratio);
|
|
1052
|
+
const droppedTokens = Math.max(0, estTotalTokens - sampleTokens);
|
|
1053
|
+
return `${head}
|
|
1054
|
+
|
|
1055
|
+
[\u2026truncated ~${droppedTokens} tokens (${droppedChars} chars) \u2014 raise BridgeOptions.maxResultTokens, or call the tool with a narrower scope (filter, head, pagination)\u2026]
|
|
1056
|
+
|
|
1057
|
+
${tail}`;
|
|
1058
|
+
}
|
|
1059
|
+
function sizePrefixToTokens(s, budget) {
|
|
1060
|
+
if (budget <= 0 || s.length === 0) return "";
|
|
1061
|
+
let size = Math.min(s.length, budget * 4);
|
|
1062
|
+
for (let iter = 0; iter < 6; iter++) {
|
|
1063
|
+
if (size <= 0) return "";
|
|
1064
|
+
const slice = s.slice(0, size);
|
|
1065
|
+
const count = countTokens(slice);
|
|
1066
|
+
if (count <= budget) return slice;
|
|
1067
|
+
const next = Math.floor(size * (budget / count) * 0.95);
|
|
1068
|
+
if (next >= size) return s.slice(0, Math.max(0, size - 1));
|
|
1069
|
+
size = next;
|
|
1070
|
+
}
|
|
1071
|
+
return s.slice(0, Math.max(0, size));
|
|
1072
|
+
}
|
|
1073
|
+
function sizeSuffixToTokens(s, budget) {
|
|
1074
|
+
if (budget <= 0 || s.length === 0) return "";
|
|
1075
|
+
let size = Math.min(s.length, budget * 4);
|
|
1076
|
+
for (let iter = 0; iter < 6; iter++) {
|
|
1077
|
+
if (size <= 0) return "";
|
|
1078
|
+
const slice = s.slice(-size);
|
|
1079
|
+
const count = countTokens(slice);
|
|
1080
|
+
if (count <= budget) return slice;
|
|
1081
|
+
const next = Math.floor(size * (budget / count) * 0.95);
|
|
1082
|
+
if (next >= size) return s.slice(-Math.max(0, size - 1));
|
|
1083
|
+
size = next;
|
|
1084
|
+
}
|
|
1085
|
+
return s.slice(-Math.max(0, size));
|
|
1086
|
+
}
|
|
860
1087
|
function blockToString(block) {
|
|
861
1088
|
if (block.type === "text") return block.text;
|
|
862
1089
|
if (block.type === "image") return `[image ${block.mimeType}, ${block.data.length} chars base64]`;
|
|
@@ -1242,19 +1469,19 @@ import {
|
|
|
1242
1469
|
chmodSync,
|
|
1243
1470
|
existsSync as existsSync2,
|
|
1244
1471
|
mkdirSync,
|
|
1245
|
-
readFileSync as
|
|
1472
|
+
readFileSync as readFileSync3,
|
|
1246
1473
|
readdirSync,
|
|
1247
1474
|
statSync,
|
|
1248
1475
|
unlinkSync,
|
|
1249
1476
|
writeFileSync
|
|
1250
1477
|
} from "fs";
|
|
1251
1478
|
import { homedir as homedir2 } from "os";
|
|
1252
|
-
import { dirname, join as
|
|
1479
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
1253
1480
|
function sessionsDir() {
|
|
1254
|
-
return
|
|
1481
|
+
return join3(homedir2(), ".reasonix", "sessions");
|
|
1255
1482
|
}
|
|
1256
1483
|
function sessionPath(name) {
|
|
1257
|
-
return
|
|
1484
|
+
return join3(sessionsDir(), `${sanitizeName(name)}.jsonl`);
|
|
1258
1485
|
}
|
|
1259
1486
|
function sanitizeName(name) {
|
|
1260
1487
|
const cleaned = name.replace(/[^\w\-\u4e00-\u9fa5]/g, "_").slice(0, 64);
|
|
@@ -1264,7 +1491,7 @@ function loadSessionMessages(name) {
|
|
|
1264
1491
|
const path = sessionPath(name);
|
|
1265
1492
|
if (!existsSync2(path)) return [];
|
|
1266
1493
|
try {
|
|
1267
|
-
const raw =
|
|
1494
|
+
const raw = readFileSync3(path, "utf8");
|
|
1268
1495
|
const out = [];
|
|
1269
1496
|
for (const line of raw.split(/\r?\n/)) {
|
|
1270
1497
|
const trimmed = line.trim();
|
|
@@ -1282,7 +1509,7 @@ function loadSessionMessages(name) {
|
|
|
1282
1509
|
}
|
|
1283
1510
|
function appendSessionMessage(name, message) {
|
|
1284
1511
|
const path = sessionPath(name);
|
|
1285
|
-
mkdirSync(
|
|
1512
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
1286
1513
|
appendFileSync(path, `${JSON.stringify(message)}
|
|
1287
1514
|
`, "utf8");
|
|
1288
1515
|
try {
|
|
@@ -1296,7 +1523,7 @@ function listSessions() {
|
|
|
1296
1523
|
try {
|
|
1297
1524
|
const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
1298
1525
|
return files.map((file) => {
|
|
1299
|
-
const path =
|
|
1526
|
+
const path = join3(dir, file);
|
|
1300
1527
|
const stat = statSync(path);
|
|
1301
1528
|
const name = file.replace(/\.jsonl$/, "");
|
|
1302
1529
|
const messageCount = countLines(path);
|
|
@@ -1317,7 +1544,7 @@ function deleteSession(name) {
|
|
|
1317
1544
|
}
|
|
1318
1545
|
function rewriteSession(name, messages) {
|
|
1319
1546
|
const path = sessionPath(name);
|
|
1320
|
-
mkdirSync(
|
|
1547
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
1321
1548
|
const body = messages.map((m) => JSON.stringify(m)).join("\n");
|
|
1322
1549
|
writeFileSync(path, body ? `${body}
|
|
1323
1550
|
` : "", "utf8");
|
|
@@ -1328,7 +1555,7 @@ function rewriteSession(name, messages) {
|
|
|
1328
1555
|
}
|
|
1329
1556
|
function countLines(path) {
|
|
1330
1557
|
try {
|
|
1331
|
-
const raw =
|
|
1558
|
+
const raw = readFileSync3(path, "utf8");
|
|
1332
1559
|
return raw.split(/\r?\n/).filter((l) => l.trim()).length;
|
|
1333
1560
|
} catch {
|
|
1334
1561
|
return 0;
|
|
@@ -1510,20 +1737,26 @@ var CacheFirstLoop = class {
|
|
|
1510
1737
|
}
|
|
1511
1738
|
/**
|
|
1512
1739
|
* Shrink the log by re-truncating oversized tool results to a tighter
|
|
1513
|
-
* cap, and persist the result back to disk so the next launch
|
|
1514
|
-
* re-inherit a fat session file. Returns a summary the TUI
|
|
1515
|
-
* display.
|
|
1740
|
+
* token cap, and persist the result back to disk so the next launch
|
|
1741
|
+
* doesn't re-inherit a fat session file. Returns a summary the TUI
|
|
1742
|
+
* can display.
|
|
1743
|
+
*
|
|
1744
|
+
* The cap is in DeepSeek V3 tokens (not chars) — so CJK text gets
|
|
1745
|
+
* capped at the same effective context footprint as English instead
|
|
1746
|
+
* of slipping past a char cap at 2× the token cost. Default 4000
|
|
1747
|
+
* tokens, matching the token-aware dispatch cap from 0.5.2.
|
|
1516
1748
|
*
|
|
1517
1749
|
* Only tool-role messages are touched (same rationale as
|
|
1518
1750
|
* {@link healLoadedMessages}). User and assistant messages carry
|
|
1519
1751
|
* authored intent we can't mechanically shrink without losing
|
|
1520
1752
|
* meaning.
|
|
1521
1753
|
*/
|
|
1522
|
-
compact(
|
|
1754
|
+
compact(maxTokens = 4e3) {
|
|
1523
1755
|
const before = this.log.toMessages();
|
|
1524
|
-
const { messages, healedCount,
|
|
1525
|
-
|
|
1526
|
-
|
|
1756
|
+
const { messages, healedCount, tokensSaved, charsSaved } = shrinkOversizedToolResultsByTokens(
|
|
1757
|
+
before,
|
|
1758
|
+
maxTokens
|
|
1759
|
+
);
|
|
1527
1760
|
if (healedCount > 0) {
|
|
1528
1761
|
this.log.compactInPlace(messages);
|
|
1529
1762
|
if (this.sessionName) {
|
|
@@ -1533,7 +1766,7 @@ var CacheFirstLoop = class {
|
|
|
1533
1766
|
}
|
|
1534
1767
|
}
|
|
1535
1768
|
}
|
|
1536
|
-
return { healedCount, charsSaved };
|
|
1769
|
+
return { healedCount, tokensSaved, charsSaved };
|
|
1537
1770
|
}
|
|
1538
1771
|
appendAndPersist(message) {
|
|
1539
1772
|
this.log.append(message);
|
|
@@ -1897,30 +2130,28 @@ var CacheFirstLoop = class {
|
|
|
1897
2130
|
const ratio = usage.promptTokens / ctxMax;
|
|
1898
2131
|
if (ratio > 0.6 && ratio <= 0.8) {
|
|
1899
2132
|
const before = usage.promptTokens;
|
|
1900
|
-
const soft = this.compact(
|
|
2133
|
+
const soft = this.compact(4e3);
|
|
1901
2134
|
if (soft.healedCount > 0) {
|
|
1902
|
-
const
|
|
1903
|
-
const after = Math.max(0, before - approxSaved);
|
|
2135
|
+
const after = Math.max(0, before - soft.tokensSaved);
|
|
1904
2136
|
yield {
|
|
1905
2137
|
turn: this._turn,
|
|
1906
2138
|
role: "warning",
|
|
1907
2139
|
content: `context ${before.toLocaleString()}/${ctxMax.toLocaleString()} (${Math.round(
|
|
1908
2140
|
ratio * 100
|
|
1909
|
-
)}%) \u2014 proactively compacted ${soft.healedCount} tool result(s) to
|
|
2141
|
+
)}%) \u2014 proactively compacted ${soft.healedCount} tool result(s) to 4k tokens, saved ${soft.tokensSaved.toLocaleString()} tokens (now ~${after.toLocaleString()}). Staying ahead of the 80% guard.`
|
|
1910
2142
|
};
|
|
1911
2143
|
}
|
|
1912
2144
|
}
|
|
1913
2145
|
}
|
|
1914
2146
|
if (usage && usage.promptTokens / ctxMax > 0.8) {
|
|
1915
2147
|
const before = usage.promptTokens;
|
|
1916
|
-
const compactResult = this.compact(
|
|
2148
|
+
const compactResult = this.compact(1e3);
|
|
1917
2149
|
if (compactResult.healedCount > 0) {
|
|
1918
|
-
const
|
|
1919
|
-
const after = before - approxSaved;
|
|
2150
|
+
const after = Math.max(0, before - compactResult.tokensSaved);
|
|
1920
2151
|
yield {
|
|
1921
2152
|
turn: this._turn,
|
|
1922
2153
|
role: "warning",
|
|
1923
|
-
content: `context ${before.toLocaleString()}/${ctxMax.toLocaleString()} \u2014 auto-compacted ${compactResult.healedCount} oversized tool result(s), saved
|
|
2154
|
+
content: `context ${before.toLocaleString()}/${ctxMax.toLocaleString()} \u2014 auto-compacted ${compactResult.healedCount} oversized tool result(s), saved ${compactResult.tokensSaved.toLocaleString()} tokens (now ~${after.toLocaleString()}). Continuing.`
|
|
1924
2155
|
};
|
|
1925
2156
|
} else {
|
|
1926
2157
|
yield {
|
|
@@ -1975,7 +2206,7 @@ ${reason}`;
|
|
|
1975
2206
|
} else {
|
|
1976
2207
|
result = await this.tools.dispatch(name, args, {
|
|
1977
2208
|
signal,
|
|
1978
|
-
|
|
2209
|
+
maxResultTokens: DEFAULT_MAX_RESULT_TOKENS
|
|
1979
2210
|
});
|
|
1980
2211
|
const postReport = await runHooks({
|
|
1981
2212
|
hooks: this.hooks,
|
|
@@ -2121,6 +2352,25 @@ function shrinkOversizedToolResults(messages, maxChars) {
|
|
|
2121
2352
|
});
|
|
2122
2353
|
return { messages: out, healedCount, healedFrom };
|
|
2123
2354
|
}
|
|
2355
|
+
function shrinkOversizedToolResultsByTokens(messages, maxTokens) {
|
|
2356
|
+
let healedCount = 0;
|
|
2357
|
+
let tokensSaved = 0;
|
|
2358
|
+
let charsSaved = 0;
|
|
2359
|
+
const out = messages.map((msg) => {
|
|
2360
|
+
if (msg.role !== "tool") return msg;
|
|
2361
|
+
const content = typeof msg.content === "string" ? msg.content : "";
|
|
2362
|
+
if (content.length <= maxTokens) return msg;
|
|
2363
|
+
const beforeTokens = countTokens(content);
|
|
2364
|
+
if (beforeTokens <= maxTokens) return msg;
|
|
2365
|
+
const truncated = truncateForModelByTokens(content, maxTokens);
|
|
2366
|
+
const afterTokens = countTokens(truncated);
|
|
2367
|
+
healedCount += 1;
|
|
2368
|
+
tokensSaved += Math.max(0, beforeTokens - afterTokens);
|
|
2369
|
+
charsSaved += Math.max(0, content.length - truncated.length);
|
|
2370
|
+
return { ...msg, content: truncated };
|
|
2371
|
+
});
|
|
2372
|
+
return { messages: out, healedCount, tokensSaved, charsSaved };
|
|
2373
|
+
}
|
|
2124
2374
|
function healLoadedMessages(messages, maxChars) {
|
|
2125
2375
|
const shrunk = shrinkOversizedToolResults(messages, maxChars);
|
|
2126
2376
|
let healedCount = shrunk.healedCount;
|
|
@@ -2177,16 +2427,16 @@ function formatLoopError(err) {
|
|
|
2177
2427
|
}
|
|
2178
2428
|
|
|
2179
2429
|
// src/project-memory.ts
|
|
2180
|
-
import { existsSync as existsSync3, readFileSync as
|
|
2181
|
-
import { join as
|
|
2430
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
2431
|
+
import { join as join4 } from "path";
|
|
2182
2432
|
var PROJECT_MEMORY_FILE = "REASONIX.md";
|
|
2183
2433
|
var PROJECT_MEMORY_MAX_CHARS = 8e3;
|
|
2184
2434
|
function readProjectMemory(rootDir) {
|
|
2185
|
-
const path =
|
|
2435
|
+
const path = join4(rootDir, PROJECT_MEMORY_FILE);
|
|
2186
2436
|
if (!existsSync3(path)) return null;
|
|
2187
2437
|
let raw;
|
|
2188
2438
|
try {
|
|
2189
|
-
raw =
|
|
2439
|
+
raw = readFileSync4(path, "utf8");
|
|
2190
2440
|
} catch {
|
|
2191
2441
|
return null;
|
|
2192
2442
|
}
|
|
@@ -2224,18 +2474,18 @@ import { createHash as createHash2 } from "crypto";
|
|
|
2224
2474
|
import {
|
|
2225
2475
|
existsSync as existsSync5,
|
|
2226
2476
|
mkdirSync as mkdirSync2,
|
|
2227
|
-
readFileSync as
|
|
2477
|
+
readFileSync as readFileSync6,
|
|
2228
2478
|
readdirSync as readdirSync3,
|
|
2229
2479
|
unlinkSync as unlinkSync2,
|
|
2230
2480
|
writeFileSync as writeFileSync2
|
|
2231
2481
|
} from "fs";
|
|
2232
2482
|
import { homedir as homedir4 } from "os";
|
|
2233
|
-
import { join as
|
|
2483
|
+
import { join as join6, resolve as resolve2 } from "path";
|
|
2234
2484
|
|
|
2235
2485
|
// src/skills.ts
|
|
2236
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2486
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
2237
2487
|
import { homedir as homedir3 } from "os";
|
|
2238
|
-
import { join as
|
|
2488
|
+
import { join as join5, resolve } from "path";
|
|
2239
2489
|
var SKILLS_DIRNAME = "skills";
|
|
2240
2490
|
var SKILL_FILE = "SKILL.md";
|
|
2241
2491
|
var SKILLS_INDEX_MAX_CHARS = 4e3;
|
|
@@ -2282,11 +2532,11 @@ var SkillStore = class {
|
|
|
2282
2532
|
const out = [];
|
|
2283
2533
|
if (this.projectRoot) {
|
|
2284
2534
|
out.push({
|
|
2285
|
-
dir:
|
|
2535
|
+
dir: join5(this.projectRoot, ".reasonix", SKILLS_DIRNAME),
|
|
2286
2536
|
scope: "project"
|
|
2287
2537
|
});
|
|
2288
2538
|
}
|
|
2289
|
-
out.push({ dir:
|
|
2539
|
+
out.push({ dir: join5(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
|
|
2290
2540
|
return out;
|
|
2291
2541
|
}
|
|
2292
2542
|
/**
|
|
@@ -2322,11 +2572,11 @@ var SkillStore = class {
|
|
|
2322
2572
|
if (!isValidSkillName(name)) return null;
|
|
2323
2573
|
for (const { dir, scope } of this.roots()) {
|
|
2324
2574
|
if (!existsSync4(dir)) continue;
|
|
2325
|
-
const dirCandidate =
|
|
2575
|
+
const dirCandidate = join5(dir, name, SKILL_FILE);
|
|
2326
2576
|
if (existsSync4(dirCandidate) && statSync2(dirCandidate).isFile()) {
|
|
2327
2577
|
return this.parse(dirCandidate, name, scope);
|
|
2328
2578
|
}
|
|
2329
|
-
const flatCandidate =
|
|
2579
|
+
const flatCandidate = join5(dir, `${name}.md`);
|
|
2330
2580
|
if (existsSync4(flatCandidate) && statSync2(flatCandidate).isFile()) {
|
|
2331
2581
|
return this.parse(flatCandidate, name, scope);
|
|
2332
2582
|
}
|
|
@@ -2341,21 +2591,21 @@ var SkillStore = class {
|
|
|
2341
2591
|
readEntry(dir, scope, entry) {
|
|
2342
2592
|
if (entry.isDirectory()) {
|
|
2343
2593
|
if (!isValidSkillName(entry.name)) return null;
|
|
2344
|
-
const file =
|
|
2594
|
+
const file = join5(dir, entry.name, SKILL_FILE);
|
|
2345
2595
|
if (!existsSync4(file)) return null;
|
|
2346
2596
|
return this.parse(file, entry.name, scope);
|
|
2347
2597
|
}
|
|
2348
2598
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
2349
2599
|
const stem = entry.name.slice(0, -3);
|
|
2350
2600
|
if (!isValidSkillName(stem)) return null;
|
|
2351
|
-
return this.parse(
|
|
2601
|
+
return this.parse(join5(dir, entry.name), stem, scope);
|
|
2352
2602
|
}
|
|
2353
2603
|
return null;
|
|
2354
2604
|
}
|
|
2355
2605
|
parse(path, stem, scope) {
|
|
2356
2606
|
let raw;
|
|
2357
2607
|
try {
|
|
2358
|
-
raw =
|
|
2608
|
+
raw = readFileSync5(path, "utf8");
|
|
2359
2609
|
} catch {
|
|
2360
2610
|
return null;
|
|
2361
2611
|
}
|
|
@@ -2487,12 +2737,12 @@ function projectHash(rootDir) {
|
|
|
2487
2737
|
}
|
|
2488
2738
|
function scopeDir(opts) {
|
|
2489
2739
|
if (opts.scope === "global") {
|
|
2490
|
-
return
|
|
2740
|
+
return join6(opts.homeDir, USER_MEMORY_DIR, "global");
|
|
2491
2741
|
}
|
|
2492
2742
|
if (!opts.projectRoot) {
|
|
2493
2743
|
throw new Error("scope=project requires a projectRoot on MemoryStore");
|
|
2494
2744
|
}
|
|
2495
|
-
return
|
|
2745
|
+
return join6(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));
|
|
2496
2746
|
}
|
|
2497
2747
|
function ensureDir(p) {
|
|
2498
2748
|
if (!existsSync5(p)) mkdirSync2(p, { recursive: true });
|
|
@@ -2540,7 +2790,7 @@ var MemoryStore = class {
|
|
|
2540
2790
|
homeDir;
|
|
2541
2791
|
projectRoot;
|
|
2542
2792
|
constructor(opts = {}) {
|
|
2543
|
-
this.homeDir = opts.homeDir ??
|
|
2793
|
+
this.homeDir = opts.homeDir ?? join6(homedir4(), ".reasonix");
|
|
2544
2794
|
this.projectRoot = opts.projectRoot ? resolve2(opts.projectRoot) : void 0;
|
|
2545
2795
|
}
|
|
2546
2796
|
/** Directory this store writes `scope` files into, creating it if needed. */
|
|
@@ -2551,7 +2801,7 @@ var MemoryStore = class {
|
|
|
2551
2801
|
}
|
|
2552
2802
|
/** Absolute path to a memory file (no existence check). */
|
|
2553
2803
|
pathFor(scope, name) {
|
|
2554
|
-
return
|
|
2804
|
+
return join6(this.dir(scope), `${sanitizeMemoryName(name)}.md`);
|
|
2555
2805
|
}
|
|
2556
2806
|
/** True iff this store is configured with a project scope available. */
|
|
2557
2807
|
hasProjectScope() {
|
|
@@ -2563,14 +2813,14 @@ var MemoryStore = class {
|
|
|
2563
2813
|
*/
|
|
2564
2814
|
loadIndex(scope) {
|
|
2565
2815
|
if (scope === "project" && !this.projectRoot) return null;
|
|
2566
|
-
const file =
|
|
2816
|
+
const file = join6(
|
|
2567
2817
|
scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot }),
|
|
2568
2818
|
MEMORY_INDEX_FILE
|
|
2569
2819
|
);
|
|
2570
2820
|
if (!existsSync5(file)) return null;
|
|
2571
2821
|
let raw;
|
|
2572
2822
|
try {
|
|
2573
|
-
raw =
|
|
2823
|
+
raw = readFileSync6(file, "utf8");
|
|
2574
2824
|
} catch {
|
|
2575
2825
|
return null;
|
|
2576
2826
|
}
|
|
@@ -2588,7 +2838,7 @@ var MemoryStore = class {
|
|
|
2588
2838
|
if (!existsSync5(file)) {
|
|
2589
2839
|
throw new Error(`memory not found: scope=${scope} name=${name}`);
|
|
2590
2840
|
}
|
|
2591
|
-
const raw =
|
|
2841
|
+
const raw = readFileSync6(file, "utf8");
|
|
2592
2842
|
const { data, body } = parseFrontmatter2(raw);
|
|
2593
2843
|
return {
|
|
2594
2844
|
name: data.name ?? name,
|
|
@@ -2650,7 +2900,7 @@ var MemoryStore = class {
|
|
|
2650
2900
|
createdAt: todayIso()
|
|
2651
2901
|
};
|
|
2652
2902
|
const dir = this.dir(input.scope);
|
|
2653
|
-
const file =
|
|
2903
|
+
const file = join6(dir, `${name}.md`);
|
|
2654
2904
|
const content = `${formatFrontmatter(entry)}${body}
|
|
2655
2905
|
`;
|
|
2656
2906
|
writeFileSync2(file, content, "utf8");
|
|
@@ -2684,7 +2934,7 @@ var MemoryStore = class {
|
|
|
2684
2934
|
return;
|
|
2685
2935
|
}
|
|
2686
2936
|
const mdFiles = files.filter((f) => f !== MEMORY_INDEX_FILE && f.endsWith(".md")).sort((a, b) => a.localeCompare(b));
|
|
2687
|
-
const indexPath =
|
|
2937
|
+
const indexPath = join6(dir, MEMORY_INDEX_FILE);
|
|
2688
2938
|
if (mdFiles.length === 0) {
|
|
2689
2939
|
if (existsSync5(indexPath)) unlinkSync2(indexPath);
|
|
2690
2940
|
return;
|
|
@@ -3717,6 +3967,50 @@ function tokenizeCommand(cmd) {
|
|
|
3717
3967
|
if (cur.length > 0) out.push(cur);
|
|
3718
3968
|
return out;
|
|
3719
3969
|
}
|
|
3970
|
+
function detectShellOperator(cmd) {
|
|
3971
|
+
const opPrefix = /^(?:2>&1|&>|\|{1,2}|&{1,2}|2>{1,2}|>{1,2}|<{1,2})/;
|
|
3972
|
+
let cur = "";
|
|
3973
|
+
let curQuoted = false;
|
|
3974
|
+
let quote = null;
|
|
3975
|
+
const check = () => {
|
|
3976
|
+
if (cur.length === 0 && !curQuoted) return null;
|
|
3977
|
+
if (!curQuoted) {
|
|
3978
|
+
const m = opPrefix.exec(cur);
|
|
3979
|
+
if (m) return m[0] ?? null;
|
|
3980
|
+
}
|
|
3981
|
+
return null;
|
|
3982
|
+
};
|
|
3983
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
3984
|
+
const ch = cmd[i];
|
|
3985
|
+
if (quote) {
|
|
3986
|
+
if (ch === quote) {
|
|
3987
|
+
quote = null;
|
|
3988
|
+
} else if (ch === "\\" && quote === '"' && i + 1 < cmd.length) {
|
|
3989
|
+
cur += cmd[++i];
|
|
3990
|
+
curQuoted = true;
|
|
3991
|
+
} else {
|
|
3992
|
+
cur += ch;
|
|
3993
|
+
curQuoted = true;
|
|
3994
|
+
}
|
|
3995
|
+
continue;
|
|
3996
|
+
}
|
|
3997
|
+
if (ch === '"' || ch === "'") {
|
|
3998
|
+
quote = ch;
|
|
3999
|
+
curQuoted = true;
|
|
4000
|
+
continue;
|
|
4001
|
+
}
|
|
4002
|
+
if (ch === " " || ch === " ") {
|
|
4003
|
+
const op = check();
|
|
4004
|
+
if (op) return op;
|
|
4005
|
+
cur = "";
|
|
4006
|
+
curQuoted = false;
|
|
4007
|
+
continue;
|
|
4008
|
+
}
|
|
4009
|
+
cur += ch;
|
|
4010
|
+
}
|
|
4011
|
+
if (quote) return null;
|
|
4012
|
+
return check();
|
|
4013
|
+
}
|
|
3720
4014
|
function isAllowed(cmd, extra = []) {
|
|
3721
4015
|
const normalized = cmd.trim().replace(/\s+/g, " ");
|
|
3722
4016
|
const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
|
|
@@ -3729,6 +4023,12 @@ function isAllowed(cmd, extra = []) {
|
|
|
3729
4023
|
async function runCommand(cmd, opts) {
|
|
3730
4024
|
const argv = tokenizeCommand(cmd);
|
|
3731
4025
|
if (argv.length === 0) throw new Error("run_command: empty command");
|
|
4026
|
+
const operator = detectShellOperator(cmd);
|
|
4027
|
+
if (operator !== null) {
|
|
4028
|
+
throw new Error(
|
|
4029
|
+
`run_command: shell operator "${operator}" is not supported \u2014 this tool spawns one process, no shell expansion. Split into separate run_command calls and combine the output in your reasoning (e.g. instead of \`grep foo *.ts | wc -l\`, call \`grep -c foo *.ts\` or two separate commands). To pass "${operator}" as a literal argument, wrap it in quotes.`
|
|
4030
|
+
);
|
|
4031
|
+
}
|
|
3732
4032
|
const timeoutMs = (opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC) * 1e3;
|
|
3733
4033
|
const maxChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
3734
4034
|
const spawnOpts = {
|
|
@@ -3906,7 +4206,7 @@ function registerShellTools(registry, opts) {
|
|
|
3906
4206
|
properties: {
|
|
3907
4207
|
command: {
|
|
3908
4208
|
type: "string",
|
|
3909
|
-
description:
|
|
4209
|
+
description: 'Full command line. Tokenized with POSIX-ish quoting; no shell expansion. Pipes (`|`), redirects (`>`, `<`, `2>`), and `&&`/`||` chaining are rejected with an error \u2014 split into separate calls instead. To pass an operator character as a literal argument (e.g. a regex), wrap it in quotes: `grep "a|b" file.txt`.'
|
|
3910
4210
|
},
|
|
3911
4211
|
timeoutSec: {
|
|
3912
4212
|
type: "integer",
|
|
@@ -4125,12 +4425,12 @@ ${i + 1}. ${r.title}`);
|
|
|
4125
4425
|
}
|
|
4126
4426
|
|
|
4127
4427
|
// src/env.ts
|
|
4128
|
-
import { readFileSync as
|
|
4428
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
4129
4429
|
import { resolve as resolve5 } from "path";
|
|
4130
4430
|
function loadDotenv(path = ".env") {
|
|
4131
4431
|
let raw;
|
|
4132
4432
|
try {
|
|
4133
|
-
raw =
|
|
4433
|
+
raw = readFileSync7(resolve5(process.cwd(), path), "utf8");
|
|
4134
4434
|
} catch {
|
|
4135
4435
|
return;
|
|
4136
4436
|
}
|
|
@@ -4149,7 +4449,7 @@ function loadDotenv(path = ".env") {
|
|
|
4149
4449
|
}
|
|
4150
4450
|
|
|
4151
4451
|
// src/transcript.ts
|
|
4152
|
-
import { createWriteStream, readFileSync as
|
|
4452
|
+
import { createWriteStream, readFileSync as readFileSync8 } from "fs";
|
|
4153
4453
|
function recordFromLoopEvent(ev, extra) {
|
|
4154
4454
|
const rec = {
|
|
4155
4455
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4200,7 +4500,7 @@ function openTranscriptFile(path, meta) {
|
|
|
4200
4500
|
return stream;
|
|
4201
4501
|
}
|
|
4202
4502
|
function readTranscript(path) {
|
|
4203
|
-
const raw =
|
|
4503
|
+
const raw = readFileSync8(path, "utf8");
|
|
4204
4504
|
return parseTranscript(raw);
|
|
4205
4505
|
}
|
|
4206
4506
|
function isPlanStateEmptyShape(s) {
|
|
@@ -5255,8 +5555,8 @@ async function trySection(load) {
|
|
|
5255
5555
|
}
|
|
5256
5556
|
|
|
5257
5557
|
// src/code/edit-blocks.ts
|
|
5258
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as
|
|
5259
|
-
import { dirname as
|
|
5558
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync9, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
5559
|
+
import { dirname as dirname4, resolve as resolve6 } from "path";
|
|
5260
5560
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
5261
5561
|
function parseEditBlocks(text) {
|
|
5262
5562
|
const out = [];
|
|
@@ -5294,11 +5594,11 @@ function applyEditBlock(block, rootDir) {
|
|
|
5294
5594
|
message: "file does not exist; to create it, use an empty SEARCH block"
|
|
5295
5595
|
};
|
|
5296
5596
|
}
|
|
5297
|
-
mkdirSync3(
|
|
5597
|
+
mkdirSync3(dirname4(absTarget), { recursive: true });
|
|
5298
5598
|
writeFileSync3(absTarget, block.replace, "utf8");
|
|
5299
5599
|
return { path: block.path, status: "created" };
|
|
5300
5600
|
}
|
|
5301
|
-
const content =
|
|
5601
|
+
const content = readFileSync9(absTarget, "utf8");
|
|
5302
5602
|
if (searchEmpty) {
|
|
5303
5603
|
return {
|
|
5304
5604
|
path: block.path,
|
|
@@ -5337,7 +5637,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
5337
5637
|
continue;
|
|
5338
5638
|
}
|
|
5339
5639
|
try {
|
|
5340
|
-
snapshots.push({ path: b.path, prevContent:
|
|
5640
|
+
snapshots.push({ path: b.path, prevContent: readFileSync9(abs, "utf8") });
|
|
5341
5641
|
} catch {
|
|
5342
5642
|
snapshots.push({ path: b.path, prevContent: null });
|
|
5343
5643
|
}
|
|
@@ -5380,8 +5680,8 @@ function sep() {
|
|
|
5380
5680
|
}
|
|
5381
5681
|
|
|
5382
5682
|
// src/code/prompt.ts
|
|
5383
|
-
import { existsSync as existsSync8, readFileSync as
|
|
5384
|
-
import { join as
|
|
5683
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
|
|
5684
|
+
import { join as join8 } from "path";
|
|
5385
5685
|
var CODE_SYSTEM_PROMPT = `You are Reasonix Code, a coding assistant. You have filesystem tools (read_file, write_file, list_directory, search_files, etc.) rooted at the user's working directory.
|
|
5386
5686
|
|
|
5387
5687
|
# Cite or shut up \u2014 non-negotiable
|
|
@@ -5502,11 +5802,11 @@ Two different rules depending on which tool:
|
|
|
5502
5802
|
`;
|
|
5503
5803
|
function codeSystemPrompt(rootDir) {
|
|
5504
5804
|
const withMemory = applyMemoryStack(CODE_SYSTEM_PROMPT, rootDir);
|
|
5505
|
-
const gitignorePath =
|
|
5805
|
+
const gitignorePath = join8(rootDir, ".gitignore");
|
|
5506
5806
|
if (!existsSync8(gitignorePath)) return withMemory;
|
|
5507
5807
|
let content;
|
|
5508
5808
|
try {
|
|
5509
|
-
content =
|
|
5809
|
+
content = readFileSync10(gitignorePath, "utf8");
|
|
5510
5810
|
} catch {
|
|
5511
5811
|
return withMemory;
|
|
5512
5812
|
}
|
|
@@ -5526,15 +5826,15 @@ ${truncated}
|
|
|
5526
5826
|
}
|
|
5527
5827
|
|
|
5528
5828
|
// src/config.ts
|
|
5529
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as
|
|
5829
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
|
|
5530
5830
|
import { homedir as homedir5 } from "os";
|
|
5531
|
-
import { dirname as
|
|
5831
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
5532
5832
|
function defaultConfigPath() {
|
|
5533
|
-
return
|
|
5833
|
+
return join9(homedir5(), ".reasonix", "config.json");
|
|
5534
5834
|
}
|
|
5535
5835
|
function readConfig(path = defaultConfigPath()) {
|
|
5536
5836
|
try {
|
|
5537
|
-
const raw =
|
|
5837
|
+
const raw = readFileSync11(path, "utf8");
|
|
5538
5838
|
const parsed = JSON.parse(raw);
|
|
5539
5839
|
if (parsed && typeof parsed === "object") return parsed;
|
|
5540
5840
|
} catch {
|
|
@@ -5542,7 +5842,7 @@ function readConfig(path = defaultConfigPath()) {
|
|
|
5542
5842
|
return {};
|
|
5543
5843
|
}
|
|
5544
5844
|
function writeConfig(cfg, path = defaultConfigPath()) {
|
|
5545
|
-
mkdirSync4(
|
|
5845
|
+
mkdirSync4(dirname5(path), { recursive: true });
|
|
5546
5846
|
writeFileSync4(path, JSON.stringify(cfg, null, 2), "utf8");
|
|
5547
5847
|
try {
|
|
5548
5848
|
chmodSync2(path, 384);
|
|
@@ -5569,25 +5869,25 @@ function redactKey(key) {
|
|
|
5569
5869
|
}
|
|
5570
5870
|
|
|
5571
5871
|
// src/version.ts
|
|
5572
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as
|
|
5872
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
5573
5873
|
import { homedir as homedir6 } from "os";
|
|
5574
|
-
import { dirname as
|
|
5575
|
-
import { fileURLToPath } from "url";
|
|
5874
|
+
import { dirname as dirname6, join as join10 } from "path";
|
|
5875
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5576
5876
|
var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
|
|
5577
5877
|
var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5578
5878
|
var LATEST_FETCH_TIMEOUT_MS = 2e3;
|
|
5579
5879
|
function readPackageVersion() {
|
|
5580
5880
|
try {
|
|
5581
|
-
let dir =
|
|
5881
|
+
let dir = dirname6(fileURLToPath2(import.meta.url));
|
|
5582
5882
|
for (let i = 0; i < 6; i++) {
|
|
5583
|
-
const p =
|
|
5883
|
+
const p = join10(dir, "package.json");
|
|
5584
5884
|
if (existsSync9(p)) {
|
|
5585
|
-
const pkg = JSON.parse(
|
|
5885
|
+
const pkg = JSON.parse(readFileSync12(p, "utf8"));
|
|
5586
5886
|
if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
|
|
5587
5887
|
return pkg.version;
|
|
5588
5888
|
}
|
|
5589
5889
|
}
|
|
5590
|
-
const parent =
|
|
5890
|
+
const parent = dirname6(dir);
|
|
5591
5891
|
if (parent === dir) break;
|
|
5592
5892
|
dir = parent;
|
|
5593
5893
|
}
|
|
@@ -5597,11 +5897,11 @@ function readPackageVersion() {
|
|
|
5597
5897
|
}
|
|
5598
5898
|
var VERSION = readPackageVersion();
|
|
5599
5899
|
function cachePath(homeDirOverride) {
|
|
5600
|
-
return
|
|
5900
|
+
return join10(homeDirOverride ?? homedir6(), ".reasonix", "version-cache.json");
|
|
5601
5901
|
}
|
|
5602
5902
|
function readCache(homeDirOverride) {
|
|
5603
5903
|
try {
|
|
5604
|
-
const raw =
|
|
5904
|
+
const raw = readFileSync12(cachePath(homeDirOverride), "utf8");
|
|
5605
5905
|
const parsed = JSON.parse(raw);
|
|
5606
5906
|
if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
|
|
5607
5907
|
return parsed;
|
|
@@ -5613,7 +5913,7 @@ function readCache(homeDirOverride) {
|
|
|
5613
5913
|
function writeCache(entry, homeDirOverride) {
|
|
5614
5914
|
try {
|
|
5615
5915
|
const p = cachePath(homeDirOverride);
|
|
5616
|
-
mkdirSync5(
|
|
5916
|
+
mkdirSync5(dirname6(p), { recursive: true });
|
|
5617
5917
|
writeFileSync5(p, JSON.stringify(entry), "utf8");
|
|
5618
5918
|
} catch {
|
|
5619
5919
|
}
|
|
@@ -5621,8 +5921,8 @@ function writeCache(entry, homeDirOverride) {
|
|
|
5621
5921
|
async function getLatestVersion(opts = {}) {
|
|
5622
5922
|
const ttl = opts.ttlMs ?? LATEST_CACHE_TTL_MS;
|
|
5623
5923
|
if (!opts.force) {
|
|
5624
|
-
const
|
|
5625
|
-
if (
|
|
5924
|
+
const cached2 = readCache(opts.homeDir);
|
|
5925
|
+
if (cached2 && Date.now() - cached2.checkedAt < ttl) return cached2.version;
|
|
5626
5926
|
}
|
|
5627
5927
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
5628
5928
|
if (!fetchImpl) return null;
|
|
@@ -5670,11 +5970,11 @@ function isNpxInstall() {
|
|
|
5670
5970
|
}
|
|
5671
5971
|
|
|
5672
5972
|
// src/usage.ts
|
|
5673
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as
|
|
5973
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
|
|
5674
5974
|
import { homedir as homedir7 } from "os";
|
|
5675
|
-
import { dirname as
|
|
5975
|
+
import { dirname as dirname7, join as join11 } from "path";
|
|
5676
5976
|
function defaultUsageLogPath(homeDirOverride) {
|
|
5677
|
-
return
|
|
5977
|
+
return join11(homeDirOverride ?? homedir7(), ".reasonix", "usage.jsonl");
|
|
5678
5978
|
}
|
|
5679
5979
|
function appendUsage(input) {
|
|
5680
5980
|
const record = {
|
|
@@ -5690,7 +5990,7 @@ function appendUsage(input) {
|
|
|
5690
5990
|
};
|
|
5691
5991
|
const path = input.path ?? defaultUsageLogPath();
|
|
5692
5992
|
try {
|
|
5693
|
-
mkdirSync6(
|
|
5993
|
+
mkdirSync6(dirname7(path), { recursive: true });
|
|
5694
5994
|
appendFileSync2(path, `${JSON.stringify(record)}
|
|
5695
5995
|
`, "utf8");
|
|
5696
5996
|
} catch {
|
|
@@ -5701,7 +6001,7 @@ function readUsageLog(path = defaultUsageLogPath()) {
|
|
|
5701
6001
|
if (!existsSync10(path)) return [];
|
|
5702
6002
|
let raw;
|
|
5703
6003
|
try {
|
|
5704
|
-
raw =
|
|
6004
|
+
raw = readFileSync13(path, "utf8");
|
|
5705
6005
|
} catch {
|
|
5706
6006
|
return [];
|
|
5707
6007
|
}
|
|
@@ -5799,6 +6099,7 @@ export {
|
|
|
5799
6099
|
CODE_SYSTEM_PROMPT,
|
|
5800
6100
|
CacheFirstLoop,
|
|
5801
6101
|
DEFAULT_MAX_RESULT_CHARS,
|
|
6102
|
+
DEFAULT_MAX_RESULT_TOKENS,
|
|
5802
6103
|
DeepSeekClient,
|
|
5803
6104
|
HOOK_EVENTS,
|
|
5804
6105
|
HOOK_SETTINGS_DIRNAME,
|
|
@@ -5848,6 +6149,7 @@ export {
|
|
|
5848
6149
|
defaultSelector,
|
|
5849
6150
|
defaultUsageLogPath,
|
|
5850
6151
|
deleteSession,
|
|
6152
|
+
detectShellOperator,
|
|
5851
6153
|
diffTranscripts,
|
|
5852
6154
|
emptyPlanState,
|
|
5853
6155
|
fetchWithRetry,
|
|
@@ -5922,6 +6224,7 @@ export {
|
|
|
5922
6224
|
stripHallucinatedToolMarkup,
|
|
5923
6225
|
tokenizeCommand,
|
|
5924
6226
|
truncateForModel,
|
|
6227
|
+
truncateForModelByTokens,
|
|
5925
6228
|
webFetch,
|
|
5926
6229
|
webSearch,
|
|
5927
6230
|
withUtf8Codepage,
|