kimiflare 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +460 -71
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -872,22 +872,18 @@ function isReadOnlyBash(command) {
|
|
|
872
872
|
return false;
|
|
873
873
|
}
|
|
874
874
|
}
|
|
875
|
-
const argCheck = COMMANDS_NEEDING_ARG_CHECK[cmd];
|
|
876
|
-
if (argCheck) {
|
|
877
|
-
return argCheck(args);
|
|
878
|
-
}
|
|
879
875
|
return READONLY_COMMANDS.has(cmd);
|
|
880
876
|
}
|
|
881
877
|
function systemPromptForMode(m) {
|
|
882
878
|
if (m === "plan") {
|
|
883
|
-
return "\n\nPLAN MODE is active. The user wants you to investigate and produce a plan WITHOUT making any changes. Do not call write, edit, or mutating bash commands. You may use read-only bash commands (e.g., git log, git diff, ls, cat) along with read/glob/grep/web-fetch. At the end, present a concise plan (bullets, files to change, approach). The user will review and then exit plan mode to execute.";
|
|
879
|
+
return "\n\nPLAN MODE is active. The user wants you to investigate and produce a plan WITHOUT making any changes. Do not call write, edit, or mutating bash commands. You may use read-only bash commands (e.g., git log, git diff, ls, cat, grep) along with read/glob/grep/web-fetch. Scripting interpreters (node, python3, ruby, perl, awk) and build/package tools (npm, cargo, go, tsc, jest, etc.) are blocked in plan mode. At the end, present a concise plan (bullets, files to change, approach). The user will review and then exit plan mode to execute.";
|
|
884
880
|
}
|
|
885
881
|
if (m === "auto") {
|
|
886
882
|
return "\n\nAUTO MODE is active. The user has opted into autonomous execution \u2014 every tool call will be auto-approved. Work efficiently, but do not take irreversible destructive actions (rm -rf, git push --force, dropping tables, etc.) without pausing to describe them in chat first. Prefer smaller reversible steps.";
|
|
887
883
|
}
|
|
888
884
|
return "";
|
|
889
885
|
}
|
|
890
|
-
var MODES, MUTATING_TOOLS, DANGEROUS_PATTERNS, GIT_READONLY_SUBCOMMANDS, READONLY_COMMANDS
|
|
886
|
+
var MODES, MUTATING_TOOLS, DANGEROUS_PATTERNS, GIT_READONLY_SUBCOMMANDS, READONLY_COMMANDS;
|
|
891
887
|
var init_mode = __esm({
|
|
892
888
|
"src/mode.ts"() {
|
|
893
889
|
"use strict";
|
|
@@ -958,16 +954,8 @@ var init_mode = __esm({
|
|
|
958
954
|
"id",
|
|
959
955
|
"whoami",
|
|
960
956
|
"groups",
|
|
961
|
-
// Dev tools (version/info only)
|
|
962
|
-
"node",
|
|
963
|
-
"npx",
|
|
964
|
-
"python3",
|
|
965
|
-
"ruby",
|
|
966
|
-
"perl",
|
|
967
957
|
// Utilities
|
|
968
958
|
"jq",
|
|
969
|
-
"yq",
|
|
970
|
-
"awk",
|
|
971
959
|
"cut",
|
|
972
960
|
"tr",
|
|
973
961
|
"base64",
|
|
@@ -990,32 +978,6 @@ var init_mode = __esm({
|
|
|
990
978
|
"ss",
|
|
991
979
|
"lsof"
|
|
992
980
|
]);
|
|
993
|
-
COMMANDS_NEEDING_ARG_CHECK = {
|
|
994
|
-
find: (args) => !args.some((a) => a === "-delete" || a === "-exec"),
|
|
995
|
-
sed: (args) => !args.some((a) => a === "-i" || a.startsWith("-i")),
|
|
996
|
-
tar: (args) => args[0] === "-tf" || args[0] === "--list",
|
|
997
|
-
unzip: (args) => args[0] === "-l",
|
|
998
|
-
curl: (args) => !args.some((a) => a === "-o" || a === "-O" || a === "-d" || a === "--data" || a.startsWith("-X")),
|
|
999
|
-
wget: (args) => !args.some((a) => a === "-O" || a === "--output-document" || a.startsWith("--post")),
|
|
1000
|
-
npm: (args) => ["list", "view", "config"].includes(args[0] ?? "") && !(args[0] === "config" && args[1] && !args[1].startsWith("get") && args[1] !== "list"),
|
|
1001
|
-
tsc: (args) => args.every(
|
|
1002
|
-
(a) => ["--noEmit", "--version", "--showConfig", "--help", "-h", "--init"].includes(a)
|
|
1003
|
-
),
|
|
1004
|
-
eslint: (args) => args.every(
|
|
1005
|
-
(a) => ["--version", "--print-config", "--help", "-h"].includes(a) || !a.startsWith("-")
|
|
1006
|
-
),
|
|
1007
|
-
prettier: (args) => args.every(
|
|
1008
|
-
(a) => ["--version", "--check", "--help", "-h"].includes(a) || !a.startsWith("-")
|
|
1009
|
-
),
|
|
1010
|
-
jest: (args) => args.every(
|
|
1011
|
-
(a) => ["--version", "--listTests", "--showConfig", "--help", "-h"].includes(a) || !a.startsWith("-")
|
|
1012
|
-
),
|
|
1013
|
-
vitest: (args) => args.every(
|
|
1014
|
-
(a) => ["--version", "--help", "-h"].includes(a) || !a.startsWith("-")
|
|
1015
|
-
),
|
|
1016
|
-
go: (args) => ["version", "env", "list", "mod"].includes(args[0] ?? "") && !(args[0] === "mod" && args[1] && !["graph", "download", "why", "verify"].includes(args[1])),
|
|
1017
|
-
cargo: (args) => ["--version", "-V", "check", "test", "metadata"].includes(args[0] ?? "") && !(args[0] === "test" && args.includes("--no-run") === false)
|
|
1018
|
-
};
|
|
1019
981
|
}
|
|
1020
982
|
});
|
|
1021
983
|
|
|
@@ -1049,7 +1011,12 @@ How to work:
|
|
|
1049
1011
|
- You have a 262k-token context window. Read as much of a file as needed rather than guessing.
|
|
1050
1012
|
- If a request is ambiguous, ask one focused question instead of making large assumptions.
|
|
1051
1013
|
- When you finish a task, stop. Do not add a closing summary.
|
|
1052
|
-
- When creating git commits, you must include \`Co-authored-by: kimiflare <kimiflare@proton.me>\` in the commit message so kimiflare is credited as a contributor. The bash tool will also auto-append this trailer when it detects git commit-creating commands
|
|
1014
|
+
- When creating git commits, you must include \`Co-authored-by: kimiflare <kimiflare@proton.me>\` in the commit message so kimiflare is credited as a contributor. The bash tool will also auto-append this trailer when it detects git commit-creating commands.
|
|
1015
|
+
|
|
1016
|
+
Tool output reduction:
|
|
1017
|
+
- Large tool outputs (grep, read, bash, web_fetch) are reduced to compact summaries by default to preserve context window.
|
|
1018
|
+
- When you see "[output reduced]" with an artifact ID, you can call \`expand_artifact\` with that ID to retrieve the full raw output if you need more detail.
|
|
1019
|
+
- You can also re-run the original tool with more targeted parameters (e.g. read with offset/limit, grep with output_mode="files") instead of expanding.`;
|
|
1053
1020
|
}
|
|
1054
1021
|
function buildSessionPrefix(opts2) {
|
|
1055
1022
|
const now2 = opts2.now ?? /* @__PURE__ */ new Date();
|
|
@@ -1103,11 +1070,6 @@ function resolvePath(cwd, input) {
|
|
|
1103
1070
|
}
|
|
1104
1071
|
return isAbsolute(input) ? input : resolve(cwd, input);
|
|
1105
1072
|
}
|
|
1106
|
-
function truncate(s, n) {
|
|
1107
|
-
if (s.length <= n) return s;
|
|
1108
|
-
return s.slice(0, n) + `
|
|
1109
|
-
... [truncated, ${s.length - n} chars omitted]`;
|
|
1110
|
-
}
|
|
1111
1073
|
function collapsePath(input, cwd, maxLen = 40) {
|
|
1112
1074
|
if (!input) return input;
|
|
1113
1075
|
let abs;
|
|
@@ -1144,7 +1106,7 @@ var init_read = __esm({
|
|
|
1144
1106
|
MAX_BYTES = 2 * 1024 * 1024;
|
|
1145
1107
|
readTool = {
|
|
1146
1108
|
name: "read",
|
|
1147
|
-
description: "Read a text file from the local filesystem. Supports optional line offset/limit. Refuses files larger than 2MB. Returns contents with 1-indexed line numbers prefixed, cat -n style.",
|
|
1109
|
+
description: "Read a text file from the local filesystem. Supports optional line offset/limit. Refuses files larger than 2MB. Returns contents with 1-indexed line numbers prefixed, cat -n style. When reading a full file without offset/limit, the output is reduced to a compact outline (imports, exports, signatures, preview) by default; use expand_artifact to retrieve the full content or specify offset/limit for a targeted slice.",
|
|
1148
1110
|
parameters: {
|
|
1149
1111
|
type: "object",
|
|
1150
1112
|
properties: {
|
|
@@ -1340,26 +1302,23 @@ ${stdout.trimEnd()}`);
|
|
|
1340
1302
|
${stderr.trimEnd()}`);
|
|
1341
1303
|
if (!stdout && !stderr) parts.push("(no output)");
|
|
1342
1304
|
const raw = parts.join("\n");
|
|
1343
|
-
const reduced = truncate(raw, OUTPUT_CAP);
|
|
1344
1305
|
resolve2({
|
|
1345
|
-
content:
|
|
1306
|
+
content: raw,
|
|
1346
1307
|
rawBytes: Buffer.byteLength(raw, "utf8"),
|
|
1347
|
-
reducedBytes: Buffer.byteLength(
|
|
1308
|
+
reducedBytes: Buffer.byteLength(raw, "utf8")
|
|
1348
1309
|
});
|
|
1349
1310
|
});
|
|
1350
1311
|
});
|
|
1351
1312
|
}
|
|
1352
|
-
var DEFAULT_TIMEOUT, MAX_TIMEOUT,
|
|
1313
|
+
var DEFAULT_TIMEOUT, MAX_TIMEOUT, bashTool;
|
|
1353
1314
|
var init_bash = __esm({
|
|
1354
1315
|
"src/tools/bash.ts"() {
|
|
1355
1316
|
"use strict";
|
|
1356
|
-
init_paths();
|
|
1357
1317
|
DEFAULT_TIMEOUT = 12e4;
|
|
1358
1318
|
MAX_TIMEOUT = 6e5;
|
|
1359
|
-
OUTPUT_CAP = 3e4;
|
|
1360
1319
|
bashTool = {
|
|
1361
1320
|
name: "bash",
|
|
1362
|
-
description: "Run a shell command via `bash -lc`. Prompts the user for permission before executing. stdout and stderr are captured
|
|
1321
|
+
description: "Run a shell command via `bash -lc`. Prompts the user for permission before executing. stdout and stderr are captured and combined. Large outputs are reduced to a compact summary by default; use expand_artifact to retrieve the full log.",
|
|
1363
1322
|
parameters: {
|
|
1364
1323
|
type: "object",
|
|
1365
1324
|
properties: {
|
|
@@ -1444,11 +1403,10 @@ async function runRipgrep(args, root, mode) {
|
|
|
1444
1403
|
const { stdout } = await pExecFile("rg", rgArgs, { maxBuffer: 10 * 1024 * 1024 });
|
|
1445
1404
|
const trimmed = stdout.trim();
|
|
1446
1405
|
if (!trimmed) return { content: "(no matches)", rawBytes: 0, reducedBytes: 0 };
|
|
1447
|
-
const reduced = truncate(trimmed, 3e4);
|
|
1448
1406
|
return {
|
|
1449
|
-
content:
|
|
1407
|
+
content: trimmed,
|
|
1450
1408
|
rawBytes: Buffer.byteLength(trimmed, "utf8"),
|
|
1451
|
-
reducedBytes: Buffer.byteLength(
|
|
1409
|
+
reducedBytes: Buffer.byteLength(trimmed, "utf8")
|
|
1452
1410
|
};
|
|
1453
1411
|
} catch (e) {
|
|
1454
1412
|
const err = e;
|
|
@@ -1487,11 +1445,10 @@ async function runJsFallback(args, root, mode) {
|
|
|
1487
1445
|
}
|
|
1488
1446
|
if (!out.length) return { content: "(no matches)", rawBytes: 0, reducedBytes: 0 };
|
|
1489
1447
|
const raw = out.join("\n");
|
|
1490
|
-
const reduced = truncate(raw, 3e4);
|
|
1491
1448
|
return {
|
|
1492
|
-
content:
|
|
1449
|
+
content: raw,
|
|
1493
1450
|
rawBytes: Buffer.byteLength(raw, "utf8"),
|
|
1494
|
-
reducedBytes: Buffer.byteLength(
|
|
1451
|
+
reducedBytes: Buffer.byteLength(raw, "utf8")
|
|
1495
1452
|
};
|
|
1496
1453
|
}
|
|
1497
1454
|
var pExecFile, cachedHasRg, grepTool;
|
|
@@ -1534,17 +1491,15 @@ var init_grep = __esm({
|
|
|
1534
1491
|
|
|
1535
1492
|
// src/tools/web-fetch.ts
|
|
1536
1493
|
import TurndownService from "turndown";
|
|
1537
|
-
var MAX_BYTES2,
|
|
1494
|
+
var MAX_BYTES2, TIMEOUT_MS, webFetchTool;
|
|
1538
1495
|
var init_web_fetch = __esm({
|
|
1539
1496
|
"src/tools/web-fetch.ts"() {
|
|
1540
1497
|
"use strict";
|
|
1541
|
-
init_paths();
|
|
1542
1498
|
MAX_BYTES2 = 1 * 1024 * 1024;
|
|
1543
|
-
MAX_OUTPUT = 1e5;
|
|
1544
1499
|
TIMEOUT_MS = 2e4;
|
|
1545
1500
|
webFetchTool = {
|
|
1546
1501
|
name: "web_fetch",
|
|
1547
|
-
description: "Fetch a URL over HTTPS and return its content. HTML pages are converted to markdown.
|
|
1502
|
+
description: "Fetch a URL over HTTPS and return its content. HTML pages are converted to markdown. Large pages are reduced to a summary by default; use expand_artifact to retrieve the full content.",
|
|
1548
1503
|
parameters: {
|
|
1549
1504
|
type: "object",
|
|
1550
1505
|
properties: {
|
|
@@ -1578,11 +1533,10 @@ ${td.turndown(bounded)}`;
|
|
|
1578
1533
|
|
|
1579
1534
|
${bounded}`;
|
|
1580
1535
|
}
|
|
1581
|
-
const reduced = truncate(raw, MAX_OUTPUT);
|
|
1582
1536
|
return {
|
|
1583
|
-
content:
|
|
1537
|
+
content: raw,
|
|
1584
1538
|
rawBytes: Buffer.byteLength(raw, "utf8"),
|
|
1585
|
-
reducedBytes: Buffer.byteLength(
|
|
1539
|
+
reducedBytes: Buffer.byteLength(raw, "utf8")
|
|
1586
1540
|
};
|
|
1587
1541
|
} finally {
|
|
1588
1542
|
clearTimeout(timer);
|
|
@@ -1674,6 +1628,423 @@ var init_tasks = __esm({
|
|
|
1674
1628
|
}
|
|
1675
1629
|
});
|
|
1676
1630
|
|
|
1631
|
+
// src/tools/artifact-store.ts
|
|
1632
|
+
var ToolArtifactStore;
|
|
1633
|
+
var init_artifact_store = __esm({
|
|
1634
|
+
"src/tools/artifact-store.ts"() {
|
|
1635
|
+
"use strict";
|
|
1636
|
+
ToolArtifactStore = class {
|
|
1637
|
+
artifacts = /* @__PURE__ */ new Map();
|
|
1638
|
+
nextId = 0;
|
|
1639
|
+
maxArtifacts;
|
|
1640
|
+
maxTotalChars;
|
|
1641
|
+
constructor(opts2) {
|
|
1642
|
+
this.maxArtifacts = opts2?.maxArtifacts ?? 500;
|
|
1643
|
+
this.maxTotalChars = opts2?.maxTotalChars ?? 2e6;
|
|
1644
|
+
}
|
|
1645
|
+
/** Store raw content and return a stable artifact ID. */
|
|
1646
|
+
store(raw) {
|
|
1647
|
+
const id = `art_${++this.nextId}`;
|
|
1648
|
+
while (this.totalChars() + raw.length > this.maxTotalChars && this.artifacts.size > 0) {
|
|
1649
|
+
this.evictOldest();
|
|
1650
|
+
}
|
|
1651
|
+
while (this.artifacts.size >= this.maxArtifacts && this.artifacts.size > 0) {
|
|
1652
|
+
this.evictOldest();
|
|
1653
|
+
}
|
|
1654
|
+
this.artifacts.set(id, raw);
|
|
1655
|
+
return id;
|
|
1656
|
+
}
|
|
1657
|
+
retrieve(id) {
|
|
1658
|
+
return this.artifacts.get(id);
|
|
1659
|
+
}
|
|
1660
|
+
has(id) {
|
|
1661
|
+
return this.artifacts.has(id);
|
|
1662
|
+
}
|
|
1663
|
+
clear() {
|
|
1664
|
+
this.artifacts.clear();
|
|
1665
|
+
this.nextId = 0;
|
|
1666
|
+
}
|
|
1667
|
+
size() {
|
|
1668
|
+
return this.artifacts.size;
|
|
1669
|
+
}
|
|
1670
|
+
totalChars() {
|
|
1671
|
+
let sum = 0;
|
|
1672
|
+
for (const raw of this.artifacts.values()) {
|
|
1673
|
+
sum += raw.length;
|
|
1674
|
+
}
|
|
1675
|
+
return sum;
|
|
1676
|
+
}
|
|
1677
|
+
evictOldest() {
|
|
1678
|
+
const first = this.artifacts.keys().next().value;
|
|
1679
|
+
if (first !== void 0) {
|
|
1680
|
+
this.artifacts.delete(first);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
// src/tools/reducer.ts
|
|
1688
|
+
function reduceToolOutput(toolName, raw, args, store, config = DEFAULT_REDUCER_CONFIG) {
|
|
1689
|
+
const rawBytes = Buffer.byteLength(raw, "utf8");
|
|
1690
|
+
const artifactId = store.store(raw);
|
|
1691
|
+
if (!config.enabled) {
|
|
1692
|
+
return { content: raw, rawBytes, reducedBytes: rawBytes, artifactId };
|
|
1693
|
+
}
|
|
1694
|
+
let reduced;
|
|
1695
|
+
let wasReduced = false;
|
|
1696
|
+
let hint;
|
|
1697
|
+
switch (toolName) {
|
|
1698
|
+
case "grep": {
|
|
1699
|
+
const r = reduceGrep(raw, args, config.grep);
|
|
1700
|
+
reduced = r.body;
|
|
1701
|
+
wasReduced = r.wasReduced;
|
|
1702
|
+
hint = r.hint;
|
|
1703
|
+
break;
|
|
1704
|
+
}
|
|
1705
|
+
case "read": {
|
|
1706
|
+
const r = reduceRead(raw, args, config.read);
|
|
1707
|
+
reduced = r.body;
|
|
1708
|
+
wasReduced = r.wasReduced;
|
|
1709
|
+
hint = r.hint;
|
|
1710
|
+
break;
|
|
1711
|
+
}
|
|
1712
|
+
case "bash": {
|
|
1713
|
+
const r = reduceBash(raw, args, config.bash);
|
|
1714
|
+
reduced = r.body;
|
|
1715
|
+
wasReduced = r.wasReduced;
|
|
1716
|
+
hint = r.hint;
|
|
1717
|
+
break;
|
|
1718
|
+
}
|
|
1719
|
+
case "web_fetch": {
|
|
1720
|
+
const r = reduceWebFetch(raw, args, config.webFetch);
|
|
1721
|
+
reduced = r.body;
|
|
1722
|
+
wasReduced = r.wasReduced;
|
|
1723
|
+
hint = r.hint;
|
|
1724
|
+
break;
|
|
1725
|
+
}
|
|
1726
|
+
default:
|
|
1727
|
+
reduced = raw;
|
|
1728
|
+
break;
|
|
1729
|
+
}
|
|
1730
|
+
if (!wasReduced) {
|
|
1731
|
+
return { content: reduced, rawBytes, reducedBytes: rawBytes, artifactId };
|
|
1732
|
+
}
|
|
1733
|
+
const footer = `[output reduced \u2014 full raw stored as artifact ${artifactId}]`;
|
|
1734
|
+
const content = hint ? `${reduced}
|
|
1735
|
+
${footer}
|
|
1736
|
+
${hint}` : `${reduced}
|
|
1737
|
+
${footer}`;
|
|
1738
|
+
const reducedBytes = Buffer.byteLength(content, "utf8");
|
|
1739
|
+
return { content, rawBytes, reducedBytes, artifactId };
|
|
1740
|
+
}
|
|
1741
|
+
function parseGrepLines(raw) {
|
|
1742
|
+
const matches = [];
|
|
1743
|
+
for (const line of raw.split("\n")) {
|
|
1744
|
+
const trimmed = line.trim();
|
|
1745
|
+
if (!trimmed) continue;
|
|
1746
|
+
const m = trimmed.match(/^(.+?):(\d+)?:(.*)$/);
|
|
1747
|
+
if (m) {
|
|
1748
|
+
matches.push({ file: m[1], line: m[2] ? parseInt(m[2], 10) : 0, text: m[3] });
|
|
1749
|
+
} else {
|
|
1750
|
+
matches.push({ file: trimmed, line: 0, text: "" });
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
return matches;
|
|
1754
|
+
}
|
|
1755
|
+
function reduceGrep(raw, args, cfg) {
|
|
1756
|
+
const isFilesMode = args.output_mode === "files";
|
|
1757
|
+
const matches = parseGrepLines(raw);
|
|
1758
|
+
if (matches.length === 0) {
|
|
1759
|
+
return { body: raw, wasReduced: false };
|
|
1760
|
+
}
|
|
1761
|
+
if (isFilesMode) {
|
|
1762
|
+
const files = [...new Set(matches.map((m) => m.file))];
|
|
1763
|
+
const lines2 = [`${files.length} file(s) matched:`, ...files];
|
|
1764
|
+
return {
|
|
1765
|
+
body: lines2.join("\n"),
|
|
1766
|
+
wasReduced: true,
|
|
1767
|
+
hint: 'Re-run with output_mode="content" for match details.'
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1771
|
+
for (const m of matches) {
|
|
1772
|
+
const list = byFile.get(m.file) ?? [];
|
|
1773
|
+
list.push(m);
|
|
1774
|
+
byFile.set(m.file, list);
|
|
1775
|
+
}
|
|
1776
|
+
const lines = [];
|
|
1777
|
+
let totalShown = 0;
|
|
1778
|
+
const totalHits = matches.length;
|
|
1779
|
+
const fileCount = byFile.size;
|
|
1780
|
+
lines.push(`Matched ${fileCount} file(s) (${totalHits} total hits):`);
|
|
1781
|
+
for (const [file, hits] of byFile) {
|
|
1782
|
+
if (totalShown >= cfg.maxTotalLines) break;
|
|
1783
|
+
lines.push(` ${file}: ${hits.length} hit(s)`);
|
|
1784
|
+
const toShow = Math.min(hits.length, cfg.maxMatchesPerFile);
|
|
1785
|
+
for (let i = 0; i < toShow; i++) {
|
|
1786
|
+
const h = hits[i];
|
|
1787
|
+
const text = h.text.length > cfg.maxLineLength ? h.text.slice(0, cfg.maxLineLength) + "\u2026" : h.text;
|
|
1788
|
+
const prefix = h.line > 0 ? ` ${h.line}:` : " ";
|
|
1789
|
+
lines.push(`${prefix}${text}`);
|
|
1790
|
+
totalShown++;
|
|
1791
|
+
if (totalShown >= cfg.maxTotalLines) break;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
if (totalShown < totalHits) {
|
|
1795
|
+
lines.push(` \u2026 (${totalHits - totalShown} more hits omitted)`);
|
|
1796
|
+
}
|
|
1797
|
+
return {
|
|
1798
|
+
body: lines.join("\n"),
|
|
1799
|
+
wasReduced: totalHits > totalShown || fileCount > 1,
|
|
1800
|
+
hint: 'Use expand_artifact for full matches, or re-run with output_mode="files" for paths only.'
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
function reduceRead(raw, args, cfg) {
|
|
1804
|
+
const hasSlice = typeof args.offset === "number" || typeof args.limit === "number";
|
|
1805
|
+
if (hasSlice) {
|
|
1806
|
+
const lines = raw.split("\n");
|
|
1807
|
+
if (lines.length > cfg.maxSliceLines) {
|
|
1808
|
+
const kept = lines.slice(0, cfg.maxSliceLines).join("\n");
|
|
1809
|
+
return {
|
|
1810
|
+
body: kept,
|
|
1811
|
+
wasReduced: true,
|
|
1812
|
+
hint: `\u2026 (${lines.length - cfg.maxSliceLines} more lines omitted)`
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
return { body: raw, wasReduced: false };
|
|
1816
|
+
}
|
|
1817
|
+
const allLines = raw.split("\n");
|
|
1818
|
+
const totalLines = allLines.length;
|
|
1819
|
+
const cleanLines = allLines.map((l) => l.replace(/^\s*\d+\t/, ""));
|
|
1820
|
+
const imports = [];
|
|
1821
|
+
const exports = [];
|
|
1822
|
+
const functions = [];
|
|
1823
|
+
const classes = [];
|
|
1824
|
+
for (let i = 0; i < cleanLines.length; i++) {
|
|
1825
|
+
const line = cleanLines[i];
|
|
1826
|
+
const lineNum = i + 1;
|
|
1827
|
+
if (/^import\s+/.test(line)) {
|
|
1828
|
+
imports.push(`${lineNum}: ${line.trim()}`);
|
|
1829
|
+
} else if (/^(?:export\s+)?class\s+\w+/.test(line)) {
|
|
1830
|
+
classes.push(`${lineNum}: ${line.trim()}`);
|
|
1831
|
+
} else if (/^export\s+/.test(line)) {
|
|
1832
|
+
exports.push(`${lineNum}: ${line.trim()}`);
|
|
1833
|
+
} else if (/^(?:async\s+)?function\s+\w+/.test(line)) {
|
|
1834
|
+
functions.push(`${lineNum}: ${line.trim()}`);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
const parts = [];
|
|
1838
|
+
parts.push(`File: ${totalLines} lines total`);
|
|
1839
|
+
if (imports.length > 0) {
|
|
1840
|
+
parts.push(`
|
|
1841
|
+
Imports (${imports.length}):`);
|
|
1842
|
+
parts.push(...imports.slice(0, Math.floor(cfg.maxOutlineLines / 4)));
|
|
1843
|
+
}
|
|
1844
|
+
if (exports.length > 0) {
|
|
1845
|
+
parts.push(`
|
|
1846
|
+
Exports (${exports.length}):`);
|
|
1847
|
+
parts.push(...exports.slice(0, Math.floor(cfg.maxOutlineLines / 4)));
|
|
1848
|
+
}
|
|
1849
|
+
if (functions.length > 0) {
|
|
1850
|
+
parts.push(`
|
|
1851
|
+
Functions (${functions.length}):`);
|
|
1852
|
+
parts.push(...functions.slice(0, Math.floor(cfg.maxOutlineLines / 4)));
|
|
1853
|
+
}
|
|
1854
|
+
if (classes.length > 0) {
|
|
1855
|
+
parts.push(`
|
|
1856
|
+
Classes (${classes.length}):`);
|
|
1857
|
+
parts.push(...classes.slice(0, Math.floor(cfg.maxOutlineLines / 4)));
|
|
1858
|
+
}
|
|
1859
|
+
const previewCount = Math.min(cfg.maxPreviewLines, totalLines);
|
|
1860
|
+
parts.push(`
|
|
1861
|
+
Preview (lines 1\u2013${previewCount}):`);
|
|
1862
|
+
parts.push(...allLines.slice(0, previewCount));
|
|
1863
|
+
return {
|
|
1864
|
+
body: parts.join("\n"),
|
|
1865
|
+
wasReduced: true,
|
|
1866
|
+
hint: "Use expand_artifact for full file, or read with offset/limit for a specific slice."
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
function reduceBash(raw, _args, cfg) {
|
|
1870
|
+
const lines = raw.split("\n");
|
|
1871
|
+
if (lines.length <= cfg.maxTotalLines) {
|
|
1872
|
+
return { body: raw, wasReduced: false };
|
|
1873
|
+
}
|
|
1874
|
+
let header = "";
|
|
1875
|
+
let bodyStart = 0;
|
|
1876
|
+
if (lines[0]?.startsWith("exit=") || lines[0]?.startsWith("(timed out")) {
|
|
1877
|
+
header = lines[0];
|
|
1878
|
+
bodyStart = 1;
|
|
1879
|
+
}
|
|
1880
|
+
const body = lines.slice(bodyStart);
|
|
1881
|
+
const isFailure = header.includes("exit=1") || raw.includes("Error:") || raw.includes("error:") || raw.includes("FAIL") || raw.includes("failed");
|
|
1882
|
+
const out = [header];
|
|
1883
|
+
if (isFailure) {
|
|
1884
|
+
const errorIndices = [];
|
|
1885
|
+
for (let i = 0; i < body.length; i++) {
|
|
1886
|
+
const line = body[i];
|
|
1887
|
+
if (/\bError\b/i.test(line) || /\berror\b/i.test(line) || /\bFAIL\b/i.test(line) || /\bfailed\b/i.test(line) || /^\s+at\s+/.test(line) || /\s+Error:\s+/.test(line)) {
|
|
1888
|
+
for (let j = Math.max(0, i - 2); j <= Math.min(body.length - 1, i + 2); j++) {
|
|
1889
|
+
if (!errorIndices.includes(j)) errorIndices.push(j);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
errorIndices.sort((a, b) => a - b);
|
|
1894
|
+
const cappedError = errorIndices.slice(0, cfg.maxErrorBlockLines);
|
|
1895
|
+
if (cappedError.length > 0) {
|
|
1896
|
+
out.push("--- error block ---");
|
|
1897
|
+
for (const idx of cappedError) {
|
|
1898
|
+
out.push(body[idx]);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
const testNames = [];
|
|
1902
|
+
for (const line of body) {
|
|
1903
|
+
const m = line.match(/(?:✗|✕|×|FAIL)\s+(.+)/) || line.match(/failing\s*\d*\s*:?\s*(.+)/i) || line.match(/Test\s+\w+\s+failed/i);
|
|
1904
|
+
if (m && m[1]) {
|
|
1905
|
+
const name = m[1].trim().slice(0, 120);
|
|
1906
|
+
if (!testNames.includes(name)) testNames.push(name);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
if (testNames.length > 0) {
|
|
1910
|
+
out.push("--- failing tests ---");
|
|
1911
|
+
out.push(...testNames.slice(0, 10));
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
const trailing = body.slice(-cfg.maxTrailingLines);
|
|
1915
|
+
out.push("--- last lines ---");
|
|
1916
|
+
out.push(...trailing);
|
|
1917
|
+
let result = out.join("\n");
|
|
1918
|
+
if (cfg.dedupeConsecutiveLines) {
|
|
1919
|
+
result = dedupeConsecutive(result);
|
|
1920
|
+
}
|
|
1921
|
+
return {
|
|
1922
|
+
body: result,
|
|
1923
|
+
wasReduced: true,
|
|
1924
|
+
hint: "Use expand_artifact for full output."
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
function dedupeConsecutive(text) {
|
|
1928
|
+
const lines = text.split("\n");
|
|
1929
|
+
const out = [];
|
|
1930
|
+
let repeatCount = 1;
|
|
1931
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1932
|
+
const line = lines[i];
|
|
1933
|
+
const next = lines[i + 1];
|
|
1934
|
+
if (next !== void 0 && next === line) {
|
|
1935
|
+
repeatCount++;
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
if (repeatCount > 2) {
|
|
1939
|
+
out.push(line);
|
|
1940
|
+
out.push(`\u2026 (${repeatCount - 1} identical lines omitted)`);
|
|
1941
|
+
} else {
|
|
1942
|
+
for (let j = 0; j < repeatCount; j++) {
|
|
1943
|
+
out.push(line);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
repeatCount = 1;
|
|
1947
|
+
}
|
|
1948
|
+
return out.join("\n");
|
|
1949
|
+
}
|
|
1950
|
+
function reduceWebFetch(raw, args, cfg) {
|
|
1951
|
+
const url = typeof args.url === "string" ? args.url : "(unknown URL)";
|
|
1952
|
+
const titleMatch = raw.match(/^#\s+(.+)$/m);
|
|
1953
|
+
const title = titleMatch ? titleMatch[1].trim() : "(no title)";
|
|
1954
|
+
const parts = [];
|
|
1955
|
+
parts.push(`Title: ${title}`);
|
|
1956
|
+
parts.push(`URL: ${url}`);
|
|
1957
|
+
const headings = raw.match(/^#{1,3}\s+.+$/gm) ?? [];
|
|
1958
|
+
if (headings.length > 0) {
|
|
1959
|
+
parts.push("\nSections:");
|
|
1960
|
+
for (const h of headings.slice(0, 10)) {
|
|
1961
|
+
parts.push(` ${h}`);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
const bodyStart = raw.indexOf("\n\n");
|
|
1965
|
+
const body = bodyStart > 0 ? raw.slice(bodyStart + 2) : raw;
|
|
1966
|
+
const excerpt = body.slice(0, cfg.maxChars).trim();
|
|
1967
|
+
if (excerpt) {
|
|
1968
|
+
parts.push(`
|
|
1969
|
+
Excerpt (${excerpt.length} chars):`);
|
|
1970
|
+
parts.push(excerpt);
|
|
1971
|
+
}
|
|
1972
|
+
if (body.length > cfg.maxChars) {
|
|
1973
|
+
parts.push(`
|
|
1974
|
+
\u2026 (${body.length - cfg.maxChars} more chars omitted)`);
|
|
1975
|
+
}
|
|
1976
|
+
return {
|
|
1977
|
+
body: parts.join("\n"),
|
|
1978
|
+
wasReduced: true,
|
|
1979
|
+
hint: "Use expand_artifact for full page content."
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
var DEFAULT_REDUCER_CONFIG;
|
|
1983
|
+
var init_reducer = __esm({
|
|
1984
|
+
"src/tools/reducer.ts"() {
|
|
1985
|
+
"use strict";
|
|
1986
|
+
DEFAULT_REDUCER_CONFIG = {
|
|
1987
|
+
enabled: true,
|
|
1988
|
+
grep: {
|
|
1989
|
+
maxTotalLines: 50,
|
|
1990
|
+
maxMatchesPerFile: 3,
|
|
1991
|
+
maxLineLength: 200,
|
|
1992
|
+
maxOutputChars: 3e3
|
|
1993
|
+
},
|
|
1994
|
+
read: {
|
|
1995
|
+
maxOutlineLines: 60,
|
|
1996
|
+
maxSliceLines: 200,
|
|
1997
|
+
maxPreviewLines: 30,
|
|
1998
|
+
maxOutputChars: 4e3
|
|
1999
|
+
},
|
|
2000
|
+
bash: {
|
|
2001
|
+
maxTotalLines: 40,
|
|
2002
|
+
maxErrorBlockLines: 20,
|
|
2003
|
+
maxTrailingLines: 20,
|
|
2004
|
+
maxOutputChars: 4e3,
|
|
2005
|
+
dedupeConsecutiveLines: true
|
|
2006
|
+
},
|
|
2007
|
+
webFetch: {
|
|
2008
|
+
maxChars: 2e3,
|
|
2009
|
+
maxHeadingChars: 500
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
});
|
|
2014
|
+
|
|
2015
|
+
// src/tools/expand-artifact.ts
|
|
2016
|
+
function makeExpandArtifactTool(store) {
|
|
2017
|
+
return {
|
|
2018
|
+
name: "expand_artifact",
|
|
2019
|
+
description: "Retrieve the full raw content of a previously reduced tool output by its artifact ID. Use this when the compact summary is insufficient and you need the complete original output.",
|
|
2020
|
+
parameters: {
|
|
2021
|
+
type: "object",
|
|
2022
|
+
properties: {
|
|
2023
|
+
artifact_id: {
|
|
2024
|
+
type: "string",
|
|
2025
|
+
description: "The artifact ID from a reduced tool output footer, e.g. art_42."
|
|
2026
|
+
}
|
|
2027
|
+
},
|
|
2028
|
+
required: ["artifact_id"],
|
|
2029
|
+
additionalProperties: false
|
|
2030
|
+
},
|
|
2031
|
+
needsPermission: false,
|
|
2032
|
+
render: (args) => ({ title: `expand ${args.artifact_id}` }),
|
|
2033
|
+
run: async (args) => {
|
|
2034
|
+
const raw = store.retrieve(args.artifact_id);
|
|
2035
|
+
if (!raw) {
|
|
2036
|
+
return `Artifact "${args.artifact_id}" not found. It may have been evicted from memory. Re-run the original tool to regenerate the output.`;
|
|
2037
|
+
}
|
|
2038
|
+
return raw;
|
|
2039
|
+
}
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
var init_expand_artifact = __esm({
|
|
2043
|
+
"src/tools/expand-artifact.ts"() {
|
|
2044
|
+
"use strict";
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
|
|
1677
2048
|
// src/tools/executor.ts
|
|
1678
2049
|
function normalizeToolOutput(result) {
|
|
1679
2050
|
if (typeof result === "string") {
|
|
@@ -1697,6 +2068,9 @@ var init_executor = __esm({
|
|
|
1697
2068
|
init_grep();
|
|
1698
2069
|
init_web_fetch();
|
|
1699
2070
|
init_tasks();
|
|
2071
|
+
init_artifact_store();
|
|
2072
|
+
init_reducer();
|
|
2073
|
+
init_expand_artifact();
|
|
1700
2074
|
ALL_TOOLS = [
|
|
1701
2075
|
readTool,
|
|
1702
2076
|
writeTool,
|
|
@@ -1710,8 +2084,11 @@ var init_executor = __esm({
|
|
|
1710
2084
|
ToolExecutor = class {
|
|
1711
2085
|
sessionAllowed = /* @__PURE__ */ new Set();
|
|
1712
2086
|
tools;
|
|
2087
|
+
artifactStore;
|
|
1713
2088
|
constructor(tools = ALL_TOOLS) {
|
|
1714
2089
|
this.tools = new Map(tools.map((t) => [t.name, t]));
|
|
2090
|
+
this.artifactStore = new ToolArtifactStore();
|
|
2091
|
+
this.tools.set("expand_artifact", makeExpandArtifactTool(this.artifactStore));
|
|
1715
2092
|
}
|
|
1716
2093
|
list() {
|
|
1717
2094
|
return [...this.tools.values()];
|
|
@@ -1725,6 +2102,9 @@ var init_executor = __esm({
|
|
|
1725
2102
|
clearSessionPermissions() {
|
|
1726
2103
|
this.sessionAllowed.clear();
|
|
1727
2104
|
}
|
|
2105
|
+
clearArtifacts() {
|
|
2106
|
+
this.artifactStore.clear();
|
|
2107
|
+
}
|
|
1728
2108
|
async run(call, askPermission, ctx) {
|
|
1729
2109
|
const tool = this.tools.get(call.name);
|
|
1730
2110
|
if (!tool) {
|
|
@@ -1764,13 +2144,21 @@ var init_executor = __esm({
|
|
|
1764
2144
|
try {
|
|
1765
2145
|
const result = await tool.run(args, ctx);
|
|
1766
2146
|
const normalized = normalizeToolOutput(result);
|
|
2147
|
+
const reduced = reduceToolOutput(
|
|
2148
|
+
call.name,
|
|
2149
|
+
normalized.content,
|
|
2150
|
+
args,
|
|
2151
|
+
this.artifactStore,
|
|
2152
|
+
DEFAULT_REDUCER_CONFIG
|
|
2153
|
+
);
|
|
1767
2154
|
return {
|
|
1768
2155
|
tool_call_id: call.id,
|
|
1769
2156
|
name: call.name,
|
|
1770
|
-
content:
|
|
2157
|
+
content: reduced.content,
|
|
1771
2158
|
ok: true,
|
|
1772
|
-
rawBytes:
|
|
1773
|
-
reducedBytes:
|
|
2159
|
+
rawBytes: reduced.rawBytes,
|
|
2160
|
+
reducedBytes: reduced.reducedBytes,
|
|
2161
|
+
artifactId: reduced.artifactId
|
|
1774
2162
|
};
|
|
1775
2163
|
} catch (e) {
|
|
1776
2164
|
const msg = `Error running ${call.name}: ${e.message ?? String(e)}`;
|
|
@@ -2921,7 +3309,7 @@ function buildRightParts(usage, contextLimit) {
|
|
|
2921
3309
|
`in ${usage.prompt_tokens}${cached ? ` (${cached} cached)` : ""}`,
|
|
2922
3310
|
`out ${usage.completion_tokens}`,
|
|
2923
3311
|
`ctx ${pct}%`,
|
|
2924
|
-
|
|
3312
|
+
`$${cost.total.toFixed(5)}`
|
|
2925
3313
|
];
|
|
2926
3314
|
}
|
|
2927
3315
|
function shortModel(m) {
|
|
@@ -5282,6 +5670,7 @@ function App({ initialCfg, initialUpdateResult }) {
|
|
|
5282
5670
|
sessionIdRef.current = null;
|
|
5283
5671
|
sessionStateRef.current = emptySessionState();
|
|
5284
5672
|
artifactStoreRef.current = new ArtifactStore();
|
|
5673
|
+
executorRef.current.clearArtifacts();
|
|
5285
5674
|
setEvents([]);
|
|
5286
5675
|
setUsage(null);
|
|
5287
5676
|
setTasks([]);
|