reasonix 0.4.28 → 0.5.2
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 +8 -0
- package/data/deepseek-tokenizer.json.gz +0 -0
- package/dist/cli/index.js +731 -85
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +76 -1
- package/dist/index.js +397 -73
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -154,6 +154,28 @@ var DeepSeekClient = class {
|
|
|
154
154
|
return null;
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Fetch the model catalog DeepSeek currently exposes. Today this is
|
|
159
|
+
* `deepseek-chat` (V3) and `deepseek-reasoner` (R1), but querying is
|
|
160
|
+
* the only way to learn about new ones without a Reasonix release.
|
|
161
|
+
* Returns null on any network/auth failure so callers can degrade
|
|
162
|
+
* gracefully — e.g. `/models` falls back to the hardcoded hint.
|
|
163
|
+
*/
|
|
164
|
+
async listModels(opts = {}) {
|
|
165
|
+
try {
|
|
166
|
+
const resp = await this._fetch(`${this.baseUrl}/models`, {
|
|
167
|
+
method: "GET",
|
|
168
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
169
|
+
signal: opts.signal
|
|
170
|
+
});
|
|
171
|
+
if (!resp.ok) return null;
|
|
172
|
+
const data = await resp.json();
|
|
173
|
+
if (!data || !Array.isArray(data.data)) return null;
|
|
174
|
+
return data;
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
157
179
|
async chat(opts) {
|
|
158
180
|
const ctrl = new AbortController();
|
|
159
181
|
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
@@ -595,6 +617,170 @@ async function runHooks(opts) {
|
|
|
595
617
|
return { event, outcomes, blocked };
|
|
596
618
|
}
|
|
597
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
|
+
|
|
598
784
|
// src/repair/flatten.ts
|
|
599
785
|
function analyzeSchema(schema) {
|
|
600
786
|
if (!schema) return { shouldFlatten: false, leafCount: 0, maxDepth: 0 };
|
|
@@ -744,7 +930,15 @@ var ToolRegistry = class {
|
|
|
744
930
|
}
|
|
745
931
|
try {
|
|
746
932
|
const result = await tool.fn(args, { signal: opts.signal });
|
|
747
|
-
|
|
933
|
+
const str = typeof result === "string" ? result : JSON.stringify(result);
|
|
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;
|
|
748
942
|
} catch (err) {
|
|
749
943
|
const e = err;
|
|
750
944
|
if (typeof e.toToolResult === "function") {
|
|
@@ -778,6 +972,7 @@ function hasDotKey(obj) {
|
|
|
778
972
|
|
|
779
973
|
// src/mcp/registry.ts
|
|
780
974
|
var DEFAULT_MAX_RESULT_CHARS = 32e3;
|
|
975
|
+
var DEFAULT_MAX_RESULT_TOKENS = 8e3;
|
|
781
976
|
async function bridgeMcpTools(client, opts = {}) {
|
|
782
977
|
const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
|
|
783
978
|
const prefix = opts.namePrefix ?? "";
|
|
@@ -834,6 +1029,61 @@ function truncateForModel(s, maxChars) {
|
|
|
834
1029
|
|
|
835
1030
|
${tail}`;
|
|
836
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
|
+
}
|
|
837
1087
|
function blockToString(block) {
|
|
838
1088
|
if (block.type === "text") return block.text;
|
|
839
1089
|
if (block.type === "image") return `[image ${block.mimeType}, ${block.data.length} chars base64]`;
|
|
@@ -1219,19 +1469,19 @@ import {
|
|
|
1219
1469
|
chmodSync,
|
|
1220
1470
|
existsSync as existsSync2,
|
|
1221
1471
|
mkdirSync,
|
|
1222
|
-
readFileSync as
|
|
1472
|
+
readFileSync as readFileSync3,
|
|
1223
1473
|
readdirSync,
|
|
1224
1474
|
statSync,
|
|
1225
1475
|
unlinkSync,
|
|
1226
1476
|
writeFileSync
|
|
1227
1477
|
} from "fs";
|
|
1228
1478
|
import { homedir as homedir2 } from "os";
|
|
1229
|
-
import { dirname, join as
|
|
1479
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
1230
1480
|
function sessionsDir() {
|
|
1231
|
-
return
|
|
1481
|
+
return join3(homedir2(), ".reasonix", "sessions");
|
|
1232
1482
|
}
|
|
1233
1483
|
function sessionPath(name) {
|
|
1234
|
-
return
|
|
1484
|
+
return join3(sessionsDir(), `${sanitizeName(name)}.jsonl`);
|
|
1235
1485
|
}
|
|
1236
1486
|
function sanitizeName(name) {
|
|
1237
1487
|
const cleaned = name.replace(/[^\w\-\u4e00-\u9fa5]/g, "_").slice(0, 64);
|
|
@@ -1241,7 +1491,7 @@ function loadSessionMessages(name) {
|
|
|
1241
1491
|
const path = sessionPath(name);
|
|
1242
1492
|
if (!existsSync2(path)) return [];
|
|
1243
1493
|
try {
|
|
1244
|
-
const raw =
|
|
1494
|
+
const raw = readFileSync3(path, "utf8");
|
|
1245
1495
|
const out = [];
|
|
1246
1496
|
for (const line of raw.split(/\r?\n/)) {
|
|
1247
1497
|
const trimmed = line.trim();
|
|
@@ -1259,7 +1509,7 @@ function loadSessionMessages(name) {
|
|
|
1259
1509
|
}
|
|
1260
1510
|
function appendSessionMessage(name, message) {
|
|
1261
1511
|
const path = sessionPath(name);
|
|
1262
|
-
mkdirSync(
|
|
1512
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
1263
1513
|
appendFileSync(path, `${JSON.stringify(message)}
|
|
1264
1514
|
`, "utf8");
|
|
1265
1515
|
try {
|
|
@@ -1273,7 +1523,7 @@ function listSessions() {
|
|
|
1273
1523
|
try {
|
|
1274
1524
|
const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
1275
1525
|
return files.map((file) => {
|
|
1276
|
-
const path =
|
|
1526
|
+
const path = join3(dir, file);
|
|
1277
1527
|
const stat = statSync(path);
|
|
1278
1528
|
const name = file.replace(/\.jsonl$/, "");
|
|
1279
1529
|
const messageCount = countLines(path);
|
|
@@ -1294,7 +1544,7 @@ function deleteSession(name) {
|
|
|
1294
1544
|
}
|
|
1295
1545
|
function rewriteSession(name, messages) {
|
|
1296
1546
|
const path = sessionPath(name);
|
|
1297
|
-
mkdirSync(
|
|
1547
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
1298
1548
|
const body = messages.map((m) => JSON.stringify(m)).join("\n");
|
|
1299
1549
|
writeFileSync(path, body ? `${body}
|
|
1300
1550
|
` : "", "utf8");
|
|
@@ -1305,7 +1555,7 @@ function rewriteSession(name, messages) {
|
|
|
1305
1555
|
}
|
|
1306
1556
|
function countLines(path) {
|
|
1307
1557
|
try {
|
|
1308
|
-
const raw =
|
|
1558
|
+
const raw = readFileSync3(path, "utf8");
|
|
1309
1559
|
return raw.split(/\r?\n/).filter((l) => l.trim()).length;
|
|
1310
1560
|
} catch {
|
|
1311
1561
|
return 0;
|
|
@@ -1314,8 +1564,8 @@ function countLines(path) {
|
|
|
1314
1564
|
|
|
1315
1565
|
// src/telemetry.ts
|
|
1316
1566
|
var DEEPSEEK_PRICING = {
|
|
1317
|
-
"deepseek-chat": { inputCacheHit: 0.
|
|
1318
|
-
"deepseek-reasoner": { inputCacheHit: 0.
|
|
1567
|
+
"deepseek-chat": { inputCacheHit: 0.028, inputCacheMiss: 0.28, output: 0.42 },
|
|
1568
|
+
"deepseek-reasoner": { inputCacheHit: 0.028, inputCacheMiss: 0.28, output: 0.42 }
|
|
1319
1569
|
};
|
|
1320
1570
|
var CLAUDE_SONNET_PRICING = { input: 3, output: 15 };
|
|
1321
1571
|
var DEEPSEEK_CONTEXT_TOKENS = {
|
|
@@ -1870,6 +2120,24 @@ var CacheFirstLoop = class {
|
|
|
1870
2120
|
return;
|
|
1871
2121
|
}
|
|
1872
2122
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[this.model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
2123
|
+
if (usage) {
|
|
2124
|
+
const ratio = usage.promptTokens / ctxMax;
|
|
2125
|
+
if (ratio > 0.6 && ratio <= 0.8) {
|
|
2126
|
+
const before = usage.promptTokens;
|
|
2127
|
+
const soft = this.compact(16e3);
|
|
2128
|
+
if (soft.healedCount > 0) {
|
|
2129
|
+
const approxSaved = Math.round(soft.charsSaved / 4);
|
|
2130
|
+
const after = Math.max(0, before - approxSaved);
|
|
2131
|
+
yield {
|
|
2132
|
+
turn: this._turn,
|
|
2133
|
+
role: "warning",
|
|
2134
|
+
content: `context ${before.toLocaleString()}/${ctxMax.toLocaleString()} (${Math.round(
|
|
2135
|
+
ratio * 100
|
|
2136
|
+
)}%) \u2014 proactively compacted ${soft.healedCount} tool result(s) to 16k, saved ~${approxSaved.toLocaleString()} tokens (now ~${after.toLocaleString()}). Staying ahead of the 80% guard.`
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
1873
2141
|
if (usage && usage.promptTokens / ctxMax > 0.8) {
|
|
1874
2142
|
const before = usage.promptTokens;
|
|
1875
2143
|
const compactResult = this.compact(4e3);
|
|
@@ -1932,7 +2200,10 @@ var CacheFirstLoop = class {
|
|
|
1932
2200
|
result = `[hook block] ${blocking?.hook.command ?? "<unknown>"}
|
|
1933
2201
|
${reason}`;
|
|
1934
2202
|
} else {
|
|
1935
|
-
result = await this.tools.dispatch(name, args, {
|
|
2203
|
+
result = await this.tools.dispatch(name, args, {
|
|
2204
|
+
signal,
|
|
2205
|
+
maxResultTokens: DEFAULT_MAX_RESULT_TOKENS
|
|
2206
|
+
});
|
|
1936
2207
|
const postReport = await runHooks({
|
|
1937
2208
|
hooks: this.hooks,
|
|
1938
2209
|
payload: {
|
|
@@ -2133,16 +2404,16 @@ function formatLoopError(err) {
|
|
|
2133
2404
|
}
|
|
2134
2405
|
|
|
2135
2406
|
// src/project-memory.ts
|
|
2136
|
-
import { existsSync as existsSync3, readFileSync as
|
|
2137
|
-
import { join as
|
|
2407
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
2408
|
+
import { join as join4 } from "path";
|
|
2138
2409
|
var PROJECT_MEMORY_FILE = "REASONIX.md";
|
|
2139
2410
|
var PROJECT_MEMORY_MAX_CHARS = 8e3;
|
|
2140
2411
|
function readProjectMemory(rootDir) {
|
|
2141
|
-
const path =
|
|
2412
|
+
const path = join4(rootDir, PROJECT_MEMORY_FILE);
|
|
2142
2413
|
if (!existsSync3(path)) return null;
|
|
2143
2414
|
let raw;
|
|
2144
2415
|
try {
|
|
2145
|
-
raw =
|
|
2416
|
+
raw = readFileSync4(path, "utf8");
|
|
2146
2417
|
} catch {
|
|
2147
2418
|
return null;
|
|
2148
2419
|
}
|
|
@@ -2180,18 +2451,18 @@ import { createHash as createHash2 } from "crypto";
|
|
|
2180
2451
|
import {
|
|
2181
2452
|
existsSync as existsSync5,
|
|
2182
2453
|
mkdirSync as mkdirSync2,
|
|
2183
|
-
readFileSync as
|
|
2454
|
+
readFileSync as readFileSync6,
|
|
2184
2455
|
readdirSync as readdirSync3,
|
|
2185
2456
|
unlinkSync as unlinkSync2,
|
|
2186
2457
|
writeFileSync as writeFileSync2
|
|
2187
2458
|
} from "fs";
|
|
2188
2459
|
import { homedir as homedir4 } from "os";
|
|
2189
|
-
import { join as
|
|
2460
|
+
import { join as join6, resolve as resolve2 } from "path";
|
|
2190
2461
|
|
|
2191
2462
|
// src/skills.ts
|
|
2192
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2463
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
2193
2464
|
import { homedir as homedir3 } from "os";
|
|
2194
|
-
import { join as
|
|
2465
|
+
import { join as join5, resolve } from "path";
|
|
2195
2466
|
var SKILLS_DIRNAME = "skills";
|
|
2196
2467
|
var SKILL_FILE = "SKILL.md";
|
|
2197
2468
|
var SKILLS_INDEX_MAX_CHARS = 4e3;
|
|
@@ -2238,11 +2509,11 @@ var SkillStore = class {
|
|
|
2238
2509
|
const out = [];
|
|
2239
2510
|
if (this.projectRoot) {
|
|
2240
2511
|
out.push({
|
|
2241
|
-
dir:
|
|
2512
|
+
dir: join5(this.projectRoot, ".reasonix", SKILLS_DIRNAME),
|
|
2242
2513
|
scope: "project"
|
|
2243
2514
|
});
|
|
2244
2515
|
}
|
|
2245
|
-
out.push({ dir:
|
|
2516
|
+
out.push({ dir: join5(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
|
|
2246
2517
|
return out;
|
|
2247
2518
|
}
|
|
2248
2519
|
/**
|
|
@@ -2278,11 +2549,11 @@ var SkillStore = class {
|
|
|
2278
2549
|
if (!isValidSkillName(name)) return null;
|
|
2279
2550
|
for (const { dir, scope } of this.roots()) {
|
|
2280
2551
|
if (!existsSync4(dir)) continue;
|
|
2281
|
-
const dirCandidate =
|
|
2552
|
+
const dirCandidate = join5(dir, name, SKILL_FILE);
|
|
2282
2553
|
if (existsSync4(dirCandidate) && statSync2(dirCandidate).isFile()) {
|
|
2283
2554
|
return this.parse(dirCandidate, name, scope);
|
|
2284
2555
|
}
|
|
2285
|
-
const flatCandidate =
|
|
2556
|
+
const flatCandidate = join5(dir, `${name}.md`);
|
|
2286
2557
|
if (existsSync4(flatCandidate) && statSync2(flatCandidate).isFile()) {
|
|
2287
2558
|
return this.parse(flatCandidate, name, scope);
|
|
2288
2559
|
}
|
|
@@ -2297,21 +2568,21 @@ var SkillStore = class {
|
|
|
2297
2568
|
readEntry(dir, scope, entry) {
|
|
2298
2569
|
if (entry.isDirectory()) {
|
|
2299
2570
|
if (!isValidSkillName(entry.name)) return null;
|
|
2300
|
-
const file =
|
|
2571
|
+
const file = join5(dir, entry.name, SKILL_FILE);
|
|
2301
2572
|
if (!existsSync4(file)) return null;
|
|
2302
2573
|
return this.parse(file, entry.name, scope);
|
|
2303
2574
|
}
|
|
2304
2575
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
2305
2576
|
const stem = entry.name.slice(0, -3);
|
|
2306
2577
|
if (!isValidSkillName(stem)) return null;
|
|
2307
|
-
return this.parse(
|
|
2578
|
+
return this.parse(join5(dir, entry.name), stem, scope);
|
|
2308
2579
|
}
|
|
2309
2580
|
return null;
|
|
2310
2581
|
}
|
|
2311
2582
|
parse(path, stem, scope) {
|
|
2312
2583
|
let raw;
|
|
2313
2584
|
try {
|
|
2314
|
-
raw =
|
|
2585
|
+
raw = readFileSync5(path, "utf8");
|
|
2315
2586
|
} catch {
|
|
2316
2587
|
return null;
|
|
2317
2588
|
}
|
|
@@ -2443,12 +2714,12 @@ function projectHash(rootDir) {
|
|
|
2443
2714
|
}
|
|
2444
2715
|
function scopeDir(opts) {
|
|
2445
2716
|
if (opts.scope === "global") {
|
|
2446
|
-
return
|
|
2717
|
+
return join6(opts.homeDir, USER_MEMORY_DIR, "global");
|
|
2447
2718
|
}
|
|
2448
2719
|
if (!opts.projectRoot) {
|
|
2449
2720
|
throw new Error("scope=project requires a projectRoot on MemoryStore");
|
|
2450
2721
|
}
|
|
2451
|
-
return
|
|
2722
|
+
return join6(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));
|
|
2452
2723
|
}
|
|
2453
2724
|
function ensureDir(p) {
|
|
2454
2725
|
if (!existsSync5(p)) mkdirSync2(p, { recursive: true });
|
|
@@ -2496,7 +2767,7 @@ var MemoryStore = class {
|
|
|
2496
2767
|
homeDir;
|
|
2497
2768
|
projectRoot;
|
|
2498
2769
|
constructor(opts = {}) {
|
|
2499
|
-
this.homeDir = opts.homeDir ??
|
|
2770
|
+
this.homeDir = opts.homeDir ?? join6(homedir4(), ".reasonix");
|
|
2500
2771
|
this.projectRoot = opts.projectRoot ? resolve2(opts.projectRoot) : void 0;
|
|
2501
2772
|
}
|
|
2502
2773
|
/** Directory this store writes `scope` files into, creating it if needed. */
|
|
@@ -2507,7 +2778,7 @@ var MemoryStore = class {
|
|
|
2507
2778
|
}
|
|
2508
2779
|
/** Absolute path to a memory file (no existence check). */
|
|
2509
2780
|
pathFor(scope, name) {
|
|
2510
|
-
return
|
|
2781
|
+
return join6(this.dir(scope), `${sanitizeMemoryName(name)}.md`);
|
|
2511
2782
|
}
|
|
2512
2783
|
/** True iff this store is configured with a project scope available. */
|
|
2513
2784
|
hasProjectScope() {
|
|
@@ -2519,14 +2790,14 @@ var MemoryStore = class {
|
|
|
2519
2790
|
*/
|
|
2520
2791
|
loadIndex(scope) {
|
|
2521
2792
|
if (scope === "project" && !this.projectRoot) return null;
|
|
2522
|
-
const file =
|
|
2793
|
+
const file = join6(
|
|
2523
2794
|
scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot }),
|
|
2524
2795
|
MEMORY_INDEX_FILE
|
|
2525
2796
|
);
|
|
2526
2797
|
if (!existsSync5(file)) return null;
|
|
2527
2798
|
let raw;
|
|
2528
2799
|
try {
|
|
2529
|
-
raw =
|
|
2800
|
+
raw = readFileSync6(file, "utf8");
|
|
2530
2801
|
} catch {
|
|
2531
2802
|
return null;
|
|
2532
2803
|
}
|
|
@@ -2544,7 +2815,7 @@ var MemoryStore = class {
|
|
|
2544
2815
|
if (!existsSync5(file)) {
|
|
2545
2816
|
throw new Error(`memory not found: scope=${scope} name=${name}`);
|
|
2546
2817
|
}
|
|
2547
|
-
const raw =
|
|
2818
|
+
const raw = readFileSync6(file, "utf8");
|
|
2548
2819
|
const { data, body } = parseFrontmatter2(raw);
|
|
2549
2820
|
return {
|
|
2550
2821
|
name: data.name ?? name,
|
|
@@ -2606,7 +2877,7 @@ var MemoryStore = class {
|
|
|
2606
2877
|
createdAt: todayIso()
|
|
2607
2878
|
};
|
|
2608
2879
|
const dir = this.dir(input.scope);
|
|
2609
|
-
const file =
|
|
2880
|
+
const file = join6(dir, `${name}.md`);
|
|
2610
2881
|
const content = `${formatFrontmatter(entry)}${body}
|
|
2611
2882
|
`;
|
|
2612
2883
|
writeFileSync2(file, content, "utf8");
|
|
@@ -2640,7 +2911,7 @@ var MemoryStore = class {
|
|
|
2640
2911
|
return;
|
|
2641
2912
|
}
|
|
2642
2913
|
const mdFiles = files.filter((f) => f !== MEMORY_INDEX_FILE && f.endsWith(".md")).sort((a, b) => a.localeCompare(b));
|
|
2643
|
-
const indexPath =
|
|
2914
|
+
const indexPath = join6(dir, MEMORY_INDEX_FILE);
|
|
2644
2915
|
if (mdFiles.length === 0) {
|
|
2645
2916
|
if (existsSync5(indexPath)) unlinkSync2(indexPath);
|
|
2646
2917
|
return;
|
|
@@ -3673,6 +3944,50 @@ function tokenizeCommand(cmd) {
|
|
|
3673
3944
|
if (cur.length > 0) out.push(cur);
|
|
3674
3945
|
return out;
|
|
3675
3946
|
}
|
|
3947
|
+
function detectShellOperator(cmd) {
|
|
3948
|
+
const opPrefix = /^(?:2>&1|&>|\|{1,2}|&{1,2}|2>{1,2}|>{1,2}|<{1,2})/;
|
|
3949
|
+
let cur = "";
|
|
3950
|
+
let curQuoted = false;
|
|
3951
|
+
let quote = null;
|
|
3952
|
+
const check = () => {
|
|
3953
|
+
if (cur.length === 0 && !curQuoted) return null;
|
|
3954
|
+
if (!curQuoted) {
|
|
3955
|
+
const m = opPrefix.exec(cur);
|
|
3956
|
+
if (m) return m[0] ?? null;
|
|
3957
|
+
}
|
|
3958
|
+
return null;
|
|
3959
|
+
};
|
|
3960
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
3961
|
+
const ch = cmd[i];
|
|
3962
|
+
if (quote) {
|
|
3963
|
+
if (ch === quote) {
|
|
3964
|
+
quote = null;
|
|
3965
|
+
} else if (ch === "\\" && quote === '"' && i + 1 < cmd.length) {
|
|
3966
|
+
cur += cmd[++i];
|
|
3967
|
+
curQuoted = true;
|
|
3968
|
+
} else {
|
|
3969
|
+
cur += ch;
|
|
3970
|
+
curQuoted = true;
|
|
3971
|
+
}
|
|
3972
|
+
continue;
|
|
3973
|
+
}
|
|
3974
|
+
if (ch === '"' || ch === "'") {
|
|
3975
|
+
quote = ch;
|
|
3976
|
+
curQuoted = true;
|
|
3977
|
+
continue;
|
|
3978
|
+
}
|
|
3979
|
+
if (ch === " " || ch === " ") {
|
|
3980
|
+
const op = check();
|
|
3981
|
+
if (op) return op;
|
|
3982
|
+
cur = "";
|
|
3983
|
+
curQuoted = false;
|
|
3984
|
+
continue;
|
|
3985
|
+
}
|
|
3986
|
+
cur += ch;
|
|
3987
|
+
}
|
|
3988
|
+
if (quote) return null;
|
|
3989
|
+
return check();
|
|
3990
|
+
}
|
|
3676
3991
|
function isAllowed(cmd, extra = []) {
|
|
3677
3992
|
const normalized = cmd.trim().replace(/\s+/g, " ");
|
|
3678
3993
|
const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
|
|
@@ -3685,6 +4000,12 @@ function isAllowed(cmd, extra = []) {
|
|
|
3685
4000
|
async function runCommand(cmd, opts) {
|
|
3686
4001
|
const argv = tokenizeCommand(cmd);
|
|
3687
4002
|
if (argv.length === 0) throw new Error("run_command: empty command");
|
|
4003
|
+
const operator = detectShellOperator(cmd);
|
|
4004
|
+
if (operator !== null) {
|
|
4005
|
+
throw new Error(
|
|
4006
|
+
`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.`
|
|
4007
|
+
);
|
|
4008
|
+
}
|
|
3688
4009
|
const timeoutMs = (opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC) * 1e3;
|
|
3689
4010
|
const maxChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
3690
4011
|
const spawnOpts = {
|
|
@@ -3862,7 +4183,7 @@ function registerShellTools(registry, opts) {
|
|
|
3862
4183
|
properties: {
|
|
3863
4184
|
command: {
|
|
3864
4185
|
type: "string",
|
|
3865
|
-
description:
|
|
4186
|
+
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`.'
|
|
3866
4187
|
},
|
|
3867
4188
|
timeoutSec: {
|
|
3868
4189
|
type: "integer",
|
|
@@ -4081,12 +4402,12 @@ ${i + 1}. ${r.title}`);
|
|
|
4081
4402
|
}
|
|
4082
4403
|
|
|
4083
4404
|
// src/env.ts
|
|
4084
|
-
import { readFileSync as
|
|
4405
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
4085
4406
|
import { resolve as resolve5 } from "path";
|
|
4086
4407
|
function loadDotenv(path = ".env") {
|
|
4087
4408
|
let raw;
|
|
4088
4409
|
try {
|
|
4089
|
-
raw =
|
|
4410
|
+
raw = readFileSync7(resolve5(process.cwd(), path), "utf8");
|
|
4090
4411
|
} catch {
|
|
4091
4412
|
return;
|
|
4092
4413
|
}
|
|
@@ -4105,7 +4426,7 @@ function loadDotenv(path = ".env") {
|
|
|
4105
4426
|
}
|
|
4106
4427
|
|
|
4107
4428
|
// src/transcript.ts
|
|
4108
|
-
import { createWriteStream, readFileSync as
|
|
4429
|
+
import { createWriteStream, readFileSync as readFileSync8 } from "fs";
|
|
4109
4430
|
function recordFromLoopEvent(ev, extra) {
|
|
4110
4431
|
const rec = {
|
|
4111
4432
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4156,7 +4477,7 @@ function openTranscriptFile(path, meta) {
|
|
|
4156
4477
|
return stream;
|
|
4157
4478
|
}
|
|
4158
4479
|
function readTranscript(path) {
|
|
4159
|
-
const raw =
|
|
4480
|
+
const raw = readFileSync8(path, "utf8");
|
|
4160
4481
|
return parseTranscript(raw);
|
|
4161
4482
|
}
|
|
4162
4483
|
function isPlanStateEmptyShape(s) {
|
|
@@ -5211,8 +5532,8 @@ async function trySection(load) {
|
|
|
5211
5532
|
}
|
|
5212
5533
|
|
|
5213
5534
|
// src/code/edit-blocks.ts
|
|
5214
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as
|
|
5215
|
-
import { dirname as
|
|
5535
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync9, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
5536
|
+
import { dirname as dirname4, resolve as resolve6 } from "path";
|
|
5216
5537
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
5217
5538
|
function parseEditBlocks(text) {
|
|
5218
5539
|
const out = [];
|
|
@@ -5250,11 +5571,11 @@ function applyEditBlock(block, rootDir) {
|
|
|
5250
5571
|
message: "file does not exist; to create it, use an empty SEARCH block"
|
|
5251
5572
|
};
|
|
5252
5573
|
}
|
|
5253
|
-
mkdirSync3(
|
|
5574
|
+
mkdirSync3(dirname4(absTarget), { recursive: true });
|
|
5254
5575
|
writeFileSync3(absTarget, block.replace, "utf8");
|
|
5255
5576
|
return { path: block.path, status: "created" };
|
|
5256
5577
|
}
|
|
5257
|
-
const content =
|
|
5578
|
+
const content = readFileSync9(absTarget, "utf8");
|
|
5258
5579
|
if (searchEmpty) {
|
|
5259
5580
|
return {
|
|
5260
5581
|
path: block.path,
|
|
@@ -5293,7 +5614,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
5293
5614
|
continue;
|
|
5294
5615
|
}
|
|
5295
5616
|
try {
|
|
5296
|
-
snapshots.push({ path: b.path, prevContent:
|
|
5617
|
+
snapshots.push({ path: b.path, prevContent: readFileSync9(abs, "utf8") });
|
|
5297
5618
|
} catch {
|
|
5298
5619
|
snapshots.push({ path: b.path, prevContent: null });
|
|
5299
5620
|
}
|
|
@@ -5336,8 +5657,8 @@ function sep() {
|
|
|
5336
5657
|
}
|
|
5337
5658
|
|
|
5338
5659
|
// src/code/prompt.ts
|
|
5339
|
-
import { existsSync as existsSync8, readFileSync as
|
|
5340
|
-
import { join as
|
|
5660
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
|
|
5661
|
+
import { join as join8 } from "path";
|
|
5341
5662
|
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.
|
|
5342
5663
|
|
|
5343
5664
|
# Cite or shut up \u2014 non-negotiable
|
|
@@ -5458,11 +5779,11 @@ Two different rules depending on which tool:
|
|
|
5458
5779
|
`;
|
|
5459
5780
|
function codeSystemPrompt(rootDir) {
|
|
5460
5781
|
const withMemory = applyMemoryStack(CODE_SYSTEM_PROMPT, rootDir);
|
|
5461
|
-
const gitignorePath =
|
|
5782
|
+
const gitignorePath = join8(rootDir, ".gitignore");
|
|
5462
5783
|
if (!existsSync8(gitignorePath)) return withMemory;
|
|
5463
5784
|
let content;
|
|
5464
5785
|
try {
|
|
5465
|
-
content =
|
|
5786
|
+
content = readFileSync10(gitignorePath, "utf8");
|
|
5466
5787
|
} catch {
|
|
5467
5788
|
return withMemory;
|
|
5468
5789
|
}
|
|
@@ -5482,15 +5803,15 @@ ${truncated}
|
|
|
5482
5803
|
}
|
|
5483
5804
|
|
|
5484
5805
|
// src/config.ts
|
|
5485
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as
|
|
5806
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
|
|
5486
5807
|
import { homedir as homedir5 } from "os";
|
|
5487
|
-
import { dirname as
|
|
5808
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
5488
5809
|
function defaultConfigPath() {
|
|
5489
|
-
return
|
|
5810
|
+
return join9(homedir5(), ".reasonix", "config.json");
|
|
5490
5811
|
}
|
|
5491
5812
|
function readConfig(path = defaultConfigPath()) {
|
|
5492
5813
|
try {
|
|
5493
|
-
const raw =
|
|
5814
|
+
const raw = readFileSync11(path, "utf8");
|
|
5494
5815
|
const parsed = JSON.parse(raw);
|
|
5495
5816
|
if (parsed && typeof parsed === "object") return parsed;
|
|
5496
5817
|
} catch {
|
|
@@ -5498,7 +5819,7 @@ function readConfig(path = defaultConfigPath()) {
|
|
|
5498
5819
|
return {};
|
|
5499
5820
|
}
|
|
5500
5821
|
function writeConfig(cfg, path = defaultConfigPath()) {
|
|
5501
|
-
mkdirSync4(
|
|
5822
|
+
mkdirSync4(dirname5(path), { recursive: true });
|
|
5502
5823
|
writeFileSync4(path, JSON.stringify(cfg, null, 2), "utf8");
|
|
5503
5824
|
try {
|
|
5504
5825
|
chmodSync2(path, 384);
|
|
@@ -5525,25 +5846,25 @@ function redactKey(key) {
|
|
|
5525
5846
|
}
|
|
5526
5847
|
|
|
5527
5848
|
// src/version.ts
|
|
5528
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as
|
|
5849
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
5529
5850
|
import { homedir as homedir6 } from "os";
|
|
5530
|
-
import { dirname as
|
|
5531
|
-
import { fileURLToPath } from "url";
|
|
5851
|
+
import { dirname as dirname6, join as join10 } from "path";
|
|
5852
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5532
5853
|
var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
|
|
5533
5854
|
var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5534
5855
|
var LATEST_FETCH_TIMEOUT_MS = 2e3;
|
|
5535
5856
|
function readPackageVersion() {
|
|
5536
5857
|
try {
|
|
5537
|
-
let dir =
|
|
5858
|
+
let dir = dirname6(fileURLToPath2(import.meta.url));
|
|
5538
5859
|
for (let i = 0; i < 6; i++) {
|
|
5539
|
-
const p =
|
|
5860
|
+
const p = join10(dir, "package.json");
|
|
5540
5861
|
if (existsSync9(p)) {
|
|
5541
|
-
const pkg = JSON.parse(
|
|
5862
|
+
const pkg = JSON.parse(readFileSync12(p, "utf8"));
|
|
5542
5863
|
if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
|
|
5543
5864
|
return pkg.version;
|
|
5544
5865
|
}
|
|
5545
5866
|
}
|
|
5546
|
-
const parent =
|
|
5867
|
+
const parent = dirname6(dir);
|
|
5547
5868
|
if (parent === dir) break;
|
|
5548
5869
|
dir = parent;
|
|
5549
5870
|
}
|
|
@@ -5553,11 +5874,11 @@ function readPackageVersion() {
|
|
|
5553
5874
|
}
|
|
5554
5875
|
var VERSION = readPackageVersion();
|
|
5555
5876
|
function cachePath(homeDirOverride) {
|
|
5556
|
-
return
|
|
5877
|
+
return join10(homeDirOverride ?? homedir6(), ".reasonix", "version-cache.json");
|
|
5557
5878
|
}
|
|
5558
5879
|
function readCache(homeDirOverride) {
|
|
5559
5880
|
try {
|
|
5560
|
-
const raw =
|
|
5881
|
+
const raw = readFileSync12(cachePath(homeDirOverride), "utf8");
|
|
5561
5882
|
const parsed = JSON.parse(raw);
|
|
5562
5883
|
if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
|
|
5563
5884
|
return parsed;
|
|
@@ -5569,7 +5890,7 @@ function readCache(homeDirOverride) {
|
|
|
5569
5890
|
function writeCache(entry, homeDirOverride) {
|
|
5570
5891
|
try {
|
|
5571
5892
|
const p = cachePath(homeDirOverride);
|
|
5572
|
-
mkdirSync5(
|
|
5893
|
+
mkdirSync5(dirname6(p), { recursive: true });
|
|
5573
5894
|
writeFileSync5(p, JSON.stringify(entry), "utf8");
|
|
5574
5895
|
} catch {
|
|
5575
5896
|
}
|
|
@@ -5577,8 +5898,8 @@ function writeCache(entry, homeDirOverride) {
|
|
|
5577
5898
|
async function getLatestVersion(opts = {}) {
|
|
5578
5899
|
const ttl = opts.ttlMs ?? LATEST_CACHE_TTL_MS;
|
|
5579
5900
|
if (!opts.force) {
|
|
5580
|
-
const
|
|
5581
|
-
if (
|
|
5901
|
+
const cached2 = readCache(opts.homeDir);
|
|
5902
|
+
if (cached2 && Date.now() - cached2.checkedAt < ttl) return cached2.version;
|
|
5582
5903
|
}
|
|
5583
5904
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
5584
5905
|
if (!fetchImpl) return null;
|
|
@@ -5626,11 +5947,11 @@ function isNpxInstall() {
|
|
|
5626
5947
|
}
|
|
5627
5948
|
|
|
5628
5949
|
// src/usage.ts
|
|
5629
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as
|
|
5950
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
|
|
5630
5951
|
import { homedir as homedir7 } from "os";
|
|
5631
|
-
import { dirname as
|
|
5952
|
+
import { dirname as dirname7, join as join11 } from "path";
|
|
5632
5953
|
function defaultUsageLogPath(homeDirOverride) {
|
|
5633
|
-
return
|
|
5954
|
+
return join11(homeDirOverride ?? homedir7(), ".reasonix", "usage.jsonl");
|
|
5634
5955
|
}
|
|
5635
5956
|
function appendUsage(input) {
|
|
5636
5957
|
const record = {
|
|
@@ -5646,7 +5967,7 @@ function appendUsage(input) {
|
|
|
5646
5967
|
};
|
|
5647
5968
|
const path = input.path ?? defaultUsageLogPath();
|
|
5648
5969
|
try {
|
|
5649
|
-
mkdirSync6(
|
|
5970
|
+
mkdirSync6(dirname7(path), { recursive: true });
|
|
5650
5971
|
appendFileSync2(path, `${JSON.stringify(record)}
|
|
5651
5972
|
`, "utf8");
|
|
5652
5973
|
} catch {
|
|
@@ -5657,7 +5978,7 @@ function readUsageLog(path = defaultUsageLogPath()) {
|
|
|
5657
5978
|
if (!existsSync10(path)) return [];
|
|
5658
5979
|
let raw;
|
|
5659
5980
|
try {
|
|
5660
|
-
raw =
|
|
5981
|
+
raw = readFileSync13(path, "utf8");
|
|
5661
5982
|
} catch {
|
|
5662
5983
|
return [];
|
|
5663
5984
|
}
|
|
@@ -5755,6 +6076,7 @@ export {
|
|
|
5755
6076
|
CODE_SYSTEM_PROMPT,
|
|
5756
6077
|
CacheFirstLoop,
|
|
5757
6078
|
DEFAULT_MAX_RESULT_CHARS,
|
|
6079
|
+
DEFAULT_MAX_RESULT_TOKENS,
|
|
5758
6080
|
DeepSeekClient,
|
|
5759
6081
|
HOOK_EVENTS,
|
|
5760
6082
|
HOOK_SETTINGS_DIRNAME,
|
|
@@ -5804,6 +6126,7 @@ export {
|
|
|
5804
6126
|
defaultSelector,
|
|
5805
6127
|
defaultUsageLogPath,
|
|
5806
6128
|
deleteSession,
|
|
6129
|
+
detectShellOperator,
|
|
5807
6130
|
diffTranscripts,
|
|
5808
6131
|
emptyPlanState,
|
|
5809
6132
|
fetchWithRetry,
|
|
@@ -5878,6 +6201,7 @@ export {
|
|
|
5878
6201
|
stripHallucinatedToolMarkup,
|
|
5879
6202
|
tokenizeCommand,
|
|
5880
6203
|
truncateForModel,
|
|
6204
|
+
truncateForModelByTokens,
|
|
5881
6205
|
webFetch,
|
|
5882
6206
|
webSearch,
|
|
5883
6207
|
withUtf8Codepage,
|