nexrall-code 0.5.2 → 0.5.12
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 +314 -49
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9810,6 +9810,7 @@ var require_client = __commonJS({
|
|
|
9810
9810
|
};
|
|
9811
9811
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
9812
9812
|
exports2.API_BASE = void 0;
|
|
9813
|
+
exports2.chooseFinalContent = chooseFinalContent;
|
|
9813
9814
|
exports2.streamChat = streamChat;
|
|
9814
9815
|
exports2.getBalance = getBalance3;
|
|
9815
9816
|
exports2.exchangeVscodeCode = exchangeVscodeCode;
|
|
@@ -9818,6 +9819,12 @@ var require_client = __commonJS({
|
|
|
9818
9819
|
var node_fetch_1 = __importDefault((init_src(), __toCommonJS(src_exports)));
|
|
9819
9820
|
var index_1 = require_auth();
|
|
9820
9821
|
exports2.API_BASE = "https://api.nexrall.com";
|
|
9822
|
+
function chooseFinalContent(rebuilt, rawContent) {
|
|
9823
|
+
if (!Array.isArray(rawContent))
|
|
9824
|
+
return rebuilt;
|
|
9825
|
+
const hasServerSideBlocks = rawContent.some((b) => b && b.type !== "text" && b.type !== "tool_use");
|
|
9826
|
+
return hasServerSideBlocks ? rawContent : rebuilt;
|
|
9827
|
+
}
|
|
9821
9828
|
function authHeaders() {
|
|
9822
9829
|
const token = (0, index_1.getToken)();
|
|
9823
9830
|
if (!token) {
|
|
@@ -9828,11 +9835,17 @@ var require_client = __commonJS({
|
|
|
9828
9835
|
Authorization: `Bearer ${token}`
|
|
9829
9836
|
};
|
|
9830
9837
|
}
|
|
9831
|
-
var MAX_RETRIES =
|
|
9838
|
+
var MAX_RETRIES = 5;
|
|
9832
9839
|
var RETRY_BASE_MS = 1e3;
|
|
9840
|
+
var RETRY_MAX_MS = 3e4;
|
|
9841
|
+
function backoffMs(attempt) {
|
|
9842
|
+
const exp = Math.min(RETRY_MAX_MS, RETRY_BASE_MS * Math.pow(2, attempt));
|
|
9843
|
+
return Math.round(exp / 2 + Math.random() * (exp / 2));
|
|
9844
|
+
}
|
|
9833
9845
|
function sleep(ms) {
|
|
9834
9846
|
return new Promise((r2) => setTimeout(r2, ms));
|
|
9835
9847
|
}
|
|
9848
|
+
var MAX_TOTAL_ATTEMPTS = (MAX_RETRIES + 1) * (MAX_RETRIES + 1);
|
|
9836
9849
|
async function streamChat(messages, options, onEvent) {
|
|
9837
9850
|
const { model, env: env2, editorContext, nexrallMd, mode, effort, clientType, abortSignal, extraTools, agents } = options;
|
|
9838
9851
|
const controller = new AbortController();
|
|
@@ -9855,6 +9868,19 @@ var require_client = __commonJS({
|
|
|
9855
9868
|
}
|
|
9856
9869
|
];
|
|
9857
9870
|
const isRetryableStreamMsg = (m2) => /overloaded|rate.?limit|temporarily|unavailable|try again|internal server error/i.test(String(m2 ?? ""));
|
|
9871
|
+
let didRetry = false;
|
|
9872
|
+
let totalAttemptsMade = 0;
|
|
9873
|
+
const reportRetry = (reason) => {
|
|
9874
|
+
totalAttemptsMade++;
|
|
9875
|
+
didRetry = true;
|
|
9876
|
+
onEvent({ type: "retry", attempt: totalAttemptsMade, maxAttempts: MAX_TOTAL_ATTEMPTS, reason });
|
|
9877
|
+
};
|
|
9878
|
+
const clearRetryIfNeeded = () => {
|
|
9879
|
+
if (didRetry) {
|
|
9880
|
+
didRetry = false;
|
|
9881
|
+
onEvent({ type: "retry_resolved" });
|
|
9882
|
+
}
|
|
9883
|
+
};
|
|
9858
9884
|
async function runAttempt() {
|
|
9859
9885
|
let response;
|
|
9860
9886
|
let lastErr;
|
|
@@ -9862,12 +9888,14 @@ var require_client = __commonJS({
|
|
|
9862
9888
|
try {
|
|
9863
9889
|
response = await (0, node_fetch_1.default)(...fetchArgs);
|
|
9864
9890
|
if (response.status === 429 && attempt < MAX_RETRIES) {
|
|
9891
|
+
reportRetry("Rate limited by the API \u2014 retrying");
|
|
9865
9892
|
const retryAfter = parseInt(response.headers.get("retry-after") ?? "0", 10);
|
|
9866
|
-
await sleep(retryAfter > 0 ? retryAfter * 1e3 :
|
|
9893
|
+
await sleep(retryAfter > 0 ? retryAfter * 1e3 : backoffMs(attempt));
|
|
9867
9894
|
continue;
|
|
9868
9895
|
}
|
|
9869
9896
|
if (response.status >= 500 && response.status < 600 && attempt < MAX_RETRIES) {
|
|
9870
|
-
|
|
9897
|
+
reportRetry(`Server error (${response.status}) \u2014 retrying`);
|
|
9898
|
+
await sleep(backoffMs(attempt));
|
|
9871
9899
|
continue;
|
|
9872
9900
|
}
|
|
9873
9901
|
break;
|
|
@@ -9876,7 +9904,8 @@ var require_client = __commonJS({
|
|
|
9876
9904
|
throw err;
|
|
9877
9905
|
lastErr = err;
|
|
9878
9906
|
if (attempt < MAX_RETRIES) {
|
|
9879
|
-
|
|
9907
|
+
reportRetry("Connection lost \u2014 attempting to reconnect");
|
|
9908
|
+
await sleep(backoffMs(attempt));
|
|
9880
9909
|
continue;
|
|
9881
9910
|
}
|
|
9882
9911
|
}
|
|
@@ -9886,14 +9915,17 @@ var require_client = __commonJS({
|
|
|
9886
9915
|
if (!response.ok) {
|
|
9887
9916
|
const errText = await response.text();
|
|
9888
9917
|
let errMsg = `API error ${response.status}`;
|
|
9918
|
+
let balance;
|
|
9889
9919
|
try {
|
|
9890
9920
|
const parsed = JSON.parse(errText);
|
|
9891
9921
|
if (parsed.error)
|
|
9892
9922
|
errMsg = parsed.error;
|
|
9923
|
+
if (typeof parsed.balance === "number")
|
|
9924
|
+
balance = parsed.balance;
|
|
9893
9925
|
} catch {
|
|
9894
9926
|
errMsg = errText || errMsg;
|
|
9895
9927
|
}
|
|
9896
|
-
throw new Error(errMsg);
|
|
9928
|
+
throw Object.assign(new Error(errMsg), { status: response.status, balance });
|
|
9897
9929
|
}
|
|
9898
9930
|
if (!response.body) {
|
|
9899
9931
|
throw new Error("Response body is null");
|
|
@@ -9916,7 +9948,8 @@ var require_client = __commonJS({
|
|
|
9916
9948
|
if (now - lastDataAt > HEARTBEAT_TIMEOUT_MS) {
|
|
9917
9949
|
clearInterval(heartbeatWatchdog);
|
|
9918
9950
|
stream.destroy?.();
|
|
9919
|
-
|
|
9951
|
+
const deadErr = new Error(emittedToCaller ? "Connection lost \u2014 no data received for 45 s. Retry your message." : "Connection lost \u2014 no data received for 45 s. Reconnecting\u2026");
|
|
9952
|
+
reject(emittedToCaller ? deadErr : Object.assign(deadErr, { retryable: true }));
|
|
9920
9953
|
return;
|
|
9921
9954
|
}
|
|
9922
9955
|
const stallLimitMs = sawModelEvent ? PROGRESS_TIMEOUT_MS : FIRST_EVENT_TIMEOUT_MS;
|
|
@@ -9951,6 +9984,7 @@ var require_client = __commonJS({
|
|
|
9951
9984
|
const evt = parsed;
|
|
9952
9985
|
lastProgressAt = Date.now();
|
|
9953
9986
|
sawModelEvent = true;
|
|
9987
|
+
clearRetryIfNeeded();
|
|
9954
9988
|
switch (evt.type) {
|
|
9955
9989
|
case "text": {
|
|
9956
9990
|
const text = typeof evt.text === "string" ? evt.text : "";
|
|
@@ -9980,15 +10014,20 @@ var require_client = __commonJS({
|
|
|
9980
10014
|
break;
|
|
9981
10015
|
}
|
|
9982
10016
|
case "message_complete": {
|
|
10017
|
+
const nestedMessage = evt.message;
|
|
10018
|
+
const stopReason = typeof nestedMessage?.stop_reason === "string" ? nestedMessage.stop_reason : null;
|
|
9983
10019
|
const contentBlocks2 = [];
|
|
9984
10020
|
const fullText2 = textParts.join("");
|
|
9985
10021
|
if (fullText2) {
|
|
9986
10022
|
contentBlocks2.push({ type: "text", text: fullText2 });
|
|
9987
10023
|
}
|
|
9988
10024
|
contentBlocks2.push(...toolUseBlocks);
|
|
10025
|
+
const rawContent = Array.isArray(nestedMessage?.content) ? nestedMessage.content : null;
|
|
10026
|
+
const finalContent = chooseFinalContent(contentBlocks2, rawContent);
|
|
9989
10027
|
completedMessage = {
|
|
9990
10028
|
role: "assistant",
|
|
9991
|
-
content:
|
|
10029
|
+
content: finalContent,
|
|
10030
|
+
stopReason
|
|
9992
10031
|
};
|
|
9993
10032
|
onEvent({ type: "message_complete", message: completedMessage });
|
|
9994
10033
|
break;
|
|
@@ -10034,6 +10073,12 @@ var require_client = __commonJS({
|
|
|
10034
10073
|
}
|
|
10035
10074
|
break;
|
|
10036
10075
|
}
|
|
10076
|
+
case "balance_status": {
|
|
10077
|
+
const balance = typeof evt.balance === "number" ? evt.balance : 0;
|
|
10078
|
+
const zero = !!evt.zero;
|
|
10079
|
+
onEvent({ type: "balance_status", balance, zero });
|
|
10080
|
+
break;
|
|
10081
|
+
}
|
|
10037
10082
|
case "done": {
|
|
10038
10083
|
onEvent({ type: "done" });
|
|
10039
10084
|
resolve3();
|
|
@@ -10097,12 +10142,15 @@ var require_client = __commonJS({
|
|
|
10097
10142
|
try {
|
|
10098
10143
|
for (let sAttempt = 0; sAttempt <= MAX_RETRIES; sAttempt++) {
|
|
10099
10144
|
try {
|
|
10100
|
-
|
|
10145
|
+
const result = await runAttempt();
|
|
10146
|
+
clearRetryIfNeeded();
|
|
10147
|
+
return result;
|
|
10101
10148
|
} catch (err) {
|
|
10102
10149
|
if (abortSignal?.aborted || controller.signal.aborted || err.name === "AbortError")
|
|
10103
10150
|
throw err;
|
|
10104
10151
|
if (err.retryable && sAttempt < MAX_RETRIES) {
|
|
10105
|
-
|
|
10152
|
+
reportRetry(err.message || "Connection interrupted \u2014 reconnecting");
|
|
10153
|
+
await sleep(backoffMs(sAttempt));
|
|
10106
10154
|
continue;
|
|
10107
10155
|
}
|
|
10108
10156
|
throw err;
|
|
@@ -12002,7 +12050,7 @@ Update these call-sites (or restore the symbol), then run a build/typecheck to c
|
|
|
12002
12050
|
args.push("-m", "200", "--regexp", pattern, resolved);
|
|
12003
12051
|
result = (0, child_process_1.spawnSync)("rg", args, spawnOpts);
|
|
12004
12052
|
} else {
|
|
12005
|
-
const args = ["-
|
|
12053
|
+
const args = ["-rnE", "--binary-files=without-match", "--color=never"];
|
|
12006
12054
|
if (ignoreCase)
|
|
12007
12055
|
args.push("-i");
|
|
12008
12056
|
if (contextLines > 0)
|
|
@@ -12031,8 +12079,10 @@ ${globalCapMatches(output)}` : "";
|
|
|
12031
12079
|
}
|
|
12032
12080
|
if (result.status === 1 && !output)
|
|
12033
12081
|
return { output: "No matches found." };
|
|
12034
|
-
if (result.status !== 0 && result.status !== 1)
|
|
12035
|
-
|
|
12082
|
+
if (result.status !== 0 && result.status !== 1) {
|
|
12083
|
+
const hint = /parenthes|bracket|brace|Unmatched|repetition-operator|invalid regex/i.test(stderr) ? ' \u2014 the pattern has invalid/unbalanced regex syntax. If you meant to match literal parentheses/brackets, escape them (e.g. "\\(", "\\)"), or simplify the pattern.' : "";
|
|
12084
|
+
return { error: (stderr || "search failed").trim() + hint };
|
|
12085
|
+
}
|
|
12036
12086
|
output = globalCapMatches(output);
|
|
12037
12087
|
return { output: output || "No matches found." };
|
|
12038
12088
|
}
|
|
@@ -12373,6 +12423,14 @@ ${diff2}${xfile}` };
|
|
|
12373
12423
|
return callback(null, address, family);
|
|
12374
12424
|
});
|
|
12375
12425
|
};
|
|
12426
|
+
let settled = false;
|
|
12427
|
+
let selfAborted = false;
|
|
12428
|
+
const finish = (result) => {
|
|
12429
|
+
if (settled)
|
|
12430
|
+
return;
|
|
12431
|
+
settled = true;
|
|
12432
|
+
resolve3(result);
|
|
12433
|
+
};
|
|
12376
12434
|
const req = transport.get(url, {
|
|
12377
12435
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
12378
12436
|
lookup: guardedLookup,
|
|
@@ -12385,15 +12443,16 @@ ${diff2}${xfile}` };
|
|
|
12385
12443
|
}, (res) => {
|
|
12386
12444
|
const status = res.statusCode ?? 0;
|
|
12387
12445
|
if ((status === 301 || status === 302 || status === 307 || status === 308) && res.headers.location) {
|
|
12446
|
+
selfAborted = true;
|
|
12388
12447
|
req.destroy();
|
|
12389
12448
|
let nextUrl;
|
|
12390
12449
|
try {
|
|
12391
12450
|
nextUrl = new URL(res.headers.location, url).href;
|
|
12392
12451
|
} catch {
|
|
12393
|
-
|
|
12452
|
+
finish({ error: `Invalid redirect location: ${res.headers.location}` });
|
|
12394
12453
|
return;
|
|
12395
12454
|
}
|
|
12396
|
-
fetchUrl({ url: nextUrl }, void 0, _redirectCount + 1).then(
|
|
12455
|
+
fetchUrl({ url: nextUrl }, void 0, _redirectCount + 1).then(finish);
|
|
12397
12456
|
return;
|
|
12398
12457
|
}
|
|
12399
12458
|
const contentType = res.headers["content-type"] ?? "";
|
|
@@ -12401,6 +12460,19 @@ ${diff2}${xfile}` };
|
|
|
12401
12460
|
const chunks = [];
|
|
12402
12461
|
let totalBytes = 0;
|
|
12403
12462
|
let truncated = false;
|
|
12463
|
+
const emitBody = () => {
|
|
12464
|
+
let body = Buffer.concat(chunks).toString("utf-8");
|
|
12465
|
+
if (isHtml)
|
|
12466
|
+
body = stripHtml(body);
|
|
12467
|
+
const truncNote = truncated ? `
|
|
12468
|
+
|
|
12469
|
+
[Truncated at ${MAX_FETCH_BYTES / 1024}KB]` : "";
|
|
12470
|
+
finish({ output: `HTTP ${status}
|
|
12471
|
+
|
|
12472
|
+
<untrusted_web_content url="${url}">
|
|
12473
|
+
${body}${truncNote}
|
|
12474
|
+
</untrusted_web_content>` });
|
|
12475
|
+
};
|
|
12404
12476
|
res.on("data", (chunk) => {
|
|
12405
12477
|
if (totalBytes + chunk.length > MAX_FETCH_BYTES) {
|
|
12406
12478
|
const remaining = MAX_FETCH_BYTES - totalBytes;
|
|
@@ -12408,31 +12480,28 @@ ${diff2}${xfile}` };
|
|
|
12408
12480
|
chunks.push(chunk.subarray(0, remaining));
|
|
12409
12481
|
totalBytes = MAX_FETCH_BYTES;
|
|
12410
12482
|
truncated = true;
|
|
12483
|
+
selfAborted = true;
|
|
12411
12484
|
req.destroy();
|
|
12485
|
+
emitBody();
|
|
12412
12486
|
} else {
|
|
12413
12487
|
chunks.push(chunk);
|
|
12414
12488
|
totalBytes += chunk.length;
|
|
12415
12489
|
}
|
|
12416
12490
|
});
|
|
12417
|
-
res.on("end",
|
|
12418
|
-
|
|
12419
|
-
if (
|
|
12420
|
-
|
|
12421
|
-
const truncNote = truncated ? `
|
|
12422
|
-
|
|
12423
|
-
[Truncated at ${MAX_FETCH_BYTES / 1024}KB]` : "";
|
|
12424
|
-
resolve3({ output: `HTTP ${status}
|
|
12425
|
-
|
|
12426
|
-
<untrusted_web_content url="${url}">
|
|
12427
|
-
${body}${truncNote}
|
|
12428
|
-
</untrusted_web_content>` });
|
|
12491
|
+
res.on("end", emitBody);
|
|
12492
|
+
res.on("error", (err) => {
|
|
12493
|
+
if (!selfAborted)
|
|
12494
|
+
finish({ error: err.message });
|
|
12429
12495
|
});
|
|
12430
|
-
res.on("error", (err) => resolve3({ error: err.message }));
|
|
12431
12496
|
});
|
|
12432
|
-
req.on("error", (err) =>
|
|
12497
|
+
req.on("error", (err) => {
|
|
12498
|
+
if (!selfAborted)
|
|
12499
|
+
finish({ error: err.message });
|
|
12500
|
+
});
|
|
12433
12501
|
req.on("timeout", () => {
|
|
12502
|
+
selfAborted = true;
|
|
12434
12503
|
req.destroy();
|
|
12435
|
-
|
|
12504
|
+
finish({ error: `Request timed out after ${DEFAULT_TIMEOUT_MS}ms` });
|
|
12436
12505
|
});
|
|
12437
12506
|
});
|
|
12438
12507
|
}
|
|
@@ -13706,6 +13775,8 @@ var require_loop = __commonJS({
|
|
|
13706
13775
|
exports2.ledgerRecord = ledgerRecord;
|
|
13707
13776
|
exports2.ledgerSummary = ledgerSummary;
|
|
13708
13777
|
exports2.pruneOldToolResults = pruneOldToolResults;
|
|
13778
|
+
exports2.estimateTokensRough = estimateTokensRough;
|
|
13779
|
+
exports2.compactMessagesForResume = compactMessagesForResume2;
|
|
13709
13780
|
exports2.runAgentLoop = runAgentLoop2;
|
|
13710
13781
|
var client_1 = require_client();
|
|
13711
13782
|
var executor_1 = require_executor();
|
|
@@ -14009,7 +14080,13 @@ ${tail}`;
|
|
|
14009
14080
|
pro: 1e6,
|
|
14010
14081
|
ultra: 1e6
|
|
14011
14082
|
};
|
|
14012
|
-
|
|
14083
|
+
function envFraction(name, fallback) {
|
|
14084
|
+
const v = Number(process.env[name]);
|
|
14085
|
+
return Number.isFinite(v) && v > 0 && v < 1 ? v : fallback;
|
|
14086
|
+
}
|
|
14087
|
+
var AUTO_PRUNE_THRESHOLD = envFraction("NEXRALL_PRUNE_THRESHOLD", 0.35);
|
|
14088
|
+
var AUTO_COMPACT_THRESHOLD = envFraction("NEXRALL_COMPACT_THRESHOLD", 0.8);
|
|
14089
|
+
var PRUNE_MIN_RECLAIM_BYTES = 256 * 1024;
|
|
14013
14090
|
var COMPACT_KEEP_MIN = 6;
|
|
14014
14091
|
var MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
14015
14092
|
function estimateBodyBytes(messages) {
|
|
@@ -14160,11 +14237,12 @@ ${tail}`;
|
|
|
14160
14237
|
var PRUNE_STUB_KEEP_CHARS = 400;
|
|
14161
14238
|
var PRUNE_MARKER = "\n\n[\u2026 ";
|
|
14162
14239
|
var PRUNE_MARKER_TAIL = " pruned to conserve context. Re-run the tool if you need the full result.]";
|
|
14163
|
-
function pruneOldToolResults(messages) {
|
|
14240
|
+
function pruneOldToolResults(messages, minReclaimBytes = 0) {
|
|
14164
14241
|
const cutoff = messages.length - PRUNE_KEEP_RECENT;
|
|
14165
14242
|
if (cutoff <= 1)
|
|
14166
14243
|
return 0;
|
|
14167
|
-
|
|
14244
|
+
const targets = [];
|
|
14245
|
+
let total = 0;
|
|
14168
14246
|
for (let i2 = 0; i2 < cutoff; i2++) {
|
|
14169
14247
|
const m2 = messages[i2];
|
|
14170
14248
|
if (!Array.isArray(m2.content))
|
|
@@ -14179,10 +14257,17 @@ ${tail}`;
|
|
|
14179
14257
|
continue;
|
|
14180
14258
|
const head = text.slice(0, PRUNE_STUB_KEEP_CHARS);
|
|
14181
14259
|
const omitted = text.length - head.length;
|
|
14182
|
-
|
|
14183
|
-
|
|
14260
|
+
targets.push({ block: b, head, omitted });
|
|
14261
|
+
total += omitted;
|
|
14184
14262
|
}
|
|
14185
14263
|
}
|
|
14264
|
+
if (total < minReclaimBytes)
|
|
14265
|
+
return 0;
|
|
14266
|
+
let reclaimed = 0;
|
|
14267
|
+
for (const { block, head, omitted } of targets) {
|
|
14268
|
+
block.content = `${head}${PRUNE_MARKER}${omitted} chars of earlier tool output${PRUNE_MARKER_TAIL}`;
|
|
14269
|
+
reclaimed += omitted;
|
|
14270
|
+
}
|
|
14186
14271
|
return reclaimed;
|
|
14187
14272
|
}
|
|
14188
14273
|
function originalTaskText(messages) {
|
|
@@ -14204,7 +14289,14 @@ ${tail}`;
|
|
|
14204
14289
|
let summary = "";
|
|
14205
14290
|
try {
|
|
14206
14291
|
const reply = await (0, client_1.streamChat)([{ role: "user", content: [{ type: "text", text: summaryPrompt }] }], {
|
|
14207
|
-
|
|
14292
|
+
// COST: the summariser is a mechanical "bullet-point this transcript" task —
|
|
14293
|
+
// its quality is indistinguishable across model tiers, so there is no reason
|
|
14294
|
+
// to run it on the user's (possibly expensive) tier. Force 'turbo' (Sonnet 5):
|
|
14295
|
+
// it is the cheapest 1M-context tier (in $2.0/1M vs Opus $2.5, Fable $3.0;
|
|
14296
|
+
// out $15/1M vs $25 / $50), so this is always ≤ the user's cost, and its 1M
|
|
14297
|
+
// window comfortably holds the transcript (capped at ~170K tokens by
|
|
14298
|
+
// transcriptOf) even on the largest sessions.
|
|
14299
|
+
model: "turbo",
|
|
14208
14300
|
mode: "ask",
|
|
14209
14301
|
// summariser must not call tools; ask-mode discourages action
|
|
14210
14302
|
env: options.env,
|
|
@@ -14235,6 +14327,66 @@ ${summary}
|
|
|
14235
14327
|
Continue the work from here.` }] });
|
|
14236
14328
|
return true;
|
|
14237
14329
|
}
|
|
14330
|
+
var RESUME_CHARS_PER_TOKEN = 4;
|
|
14331
|
+
function estimateTokensRough(messages) {
|
|
14332
|
+
return Math.ceil(estimateBodyBytes(messages) / RESUME_CHARS_PER_TOKEN);
|
|
14333
|
+
}
|
|
14334
|
+
async function compactMessagesForResume2(messages, opts) {
|
|
14335
|
+
if (messages.length <= COMPACT_KEEP_MIN + 2)
|
|
14336
|
+
return false;
|
|
14337
|
+
const settings = (0, rules_1.loadSettings)(opts.workDir);
|
|
14338
|
+
if (!resolveAutoCompact(void 0, settings.raw))
|
|
14339
|
+
return false;
|
|
14340
|
+
const contextWindow = MODEL_CONTEXT_TOKENS[opts.model ?? "turbo"] ?? 1e6;
|
|
14341
|
+
let bodyBytes = estimateBodyBytes(messages);
|
|
14342
|
+
let tokenGuess = estimateTokensRough(messages);
|
|
14343
|
+
const overPruneThreshold = () => tokenGuess > contextWindow * AUTO_PRUNE_THRESHOLD || bodyBytes > MAX_BODY_BYTES;
|
|
14344
|
+
const overCompactThreshold = () => tokenGuess > contextWindow * AUTO_COMPACT_THRESHOLD || bodyBytes > MAX_BODY_BYTES;
|
|
14345
|
+
if (!overPruneThreshold())
|
|
14346
|
+
return false;
|
|
14347
|
+
let compacted = false;
|
|
14348
|
+
if (messages.length > PRUNE_KEEP_RECENT + 2) {
|
|
14349
|
+
const reclaimed = pruneOldToolResults(messages, PRUNE_MIN_RECLAIM_BYTES);
|
|
14350
|
+
if (reclaimed > 0) {
|
|
14351
|
+
bodyBytes = estimateBodyBytes(messages);
|
|
14352
|
+
tokenGuess = estimateTokensRough(messages);
|
|
14353
|
+
compacted = true;
|
|
14354
|
+
opts.onNotice?.(`
|
|
14355
|
+
\u267B\uFE0F Trimmed ~${(reclaimed / (1024 * 1024)).toFixed(1)}MB of older tool output before resuming this chat.
|
|
14356
|
+
`);
|
|
14357
|
+
}
|
|
14358
|
+
}
|
|
14359
|
+
let guard = 0;
|
|
14360
|
+
while (overCompactThreshold() && messages.length > COMPACT_KEEP_MIN + 2 && guard < 5) {
|
|
14361
|
+
guard += 1;
|
|
14362
|
+
const did = await autoCompactMessages(messages, {
|
|
14363
|
+
workDir: opts.workDir,
|
|
14364
|
+
model: opts.model,
|
|
14365
|
+
clientType: opts.clientType,
|
|
14366
|
+
env: opts.env,
|
|
14367
|
+
onText: () => {
|
|
14368
|
+
},
|
|
14369
|
+
onToolUse: () => {
|
|
14370
|
+
},
|
|
14371
|
+
onToolResult: () => {
|
|
14372
|
+
},
|
|
14373
|
+
onUsage: () => {
|
|
14374
|
+
},
|
|
14375
|
+
requestPermission: async () => false
|
|
14376
|
+
});
|
|
14377
|
+
if (!did)
|
|
14378
|
+
break;
|
|
14379
|
+
compacted = true;
|
|
14380
|
+
bodyBytes = estimateBodyBytes(messages);
|
|
14381
|
+
tokenGuess = estimateTokensRough(messages);
|
|
14382
|
+
}
|
|
14383
|
+
if (compacted) {
|
|
14384
|
+
opts.onNotice?.(`
|
|
14385
|
+
\u267B\uFE0F Auto-compacted this chat's earlier history before resuming, to avoid resending it at full cost.
|
|
14386
|
+
`);
|
|
14387
|
+
}
|
|
14388
|
+
return compacted;
|
|
14389
|
+
}
|
|
14238
14390
|
async function runAgentLoop2(initialMessages, options) {
|
|
14239
14391
|
const messages = [...initialMessages];
|
|
14240
14392
|
const model = options.model ?? "turbo";
|
|
@@ -14270,16 +14422,15 @@ Continue the work from here.` }] });
|
|
|
14270
14422
|
if (options.abortSignal?.aborted)
|
|
14271
14423
|
break;
|
|
14272
14424
|
let bodyBytes = estimateBodyBytes(messages);
|
|
14425
|
+
const prunePressure = lastPromptTokens > contextWindow * AUTO_PRUNE_THRESHOLD;
|
|
14273
14426
|
const tokenPressure = lastPromptTokens > contextWindow * AUTO_COMPACT_THRESHOLD;
|
|
14274
14427
|
let bytePressure = bodyBytes > MAX_BODY_BYTES;
|
|
14275
|
-
if (autoCompact && !compacting && bytePressure && messages.length > PRUNE_KEEP_RECENT + 2) {
|
|
14276
|
-
const reclaimed = pruneOldToolResults(messages);
|
|
14428
|
+
if (autoCompact && !compacting && (prunePressure || bytePressure) && messages.length > PRUNE_KEEP_RECENT + 2) {
|
|
14429
|
+
const reclaimed = pruneOldToolResults(messages, PRUNE_MIN_RECLAIM_BYTES);
|
|
14277
14430
|
if (reclaimed > 0) {
|
|
14278
14431
|
bodyBytes = estimateBodyBytes(messages);
|
|
14279
14432
|
bytePressure = bodyBytes > MAX_BODY_BYTES;
|
|
14280
|
-
options.onText(
|
|
14281
|
-
\u267B\uFE0F Trimmed ~${(reclaimed / (1024 * 1024)).toFixed(1)}MB of older tool output to conserve context.
|
|
14282
|
-
`);
|
|
14433
|
+
(options.onNotice ?? options.onText)(`\u267B\uFE0F Trimmed ~${(reclaimed / (1024 * 1024)).toFixed(1)}MB of already-processed tool output to keep this chat cheap to continue.`);
|
|
14283
14434
|
}
|
|
14284
14435
|
}
|
|
14285
14436
|
if (autoCompact && !compacting && (tokenPressure || bytePressure) && messages.length > COMPACT_KEEP_MIN + 2) {
|
|
@@ -14289,9 +14440,7 @@ Continue the work from here.` }] });
|
|
|
14289
14440
|
if (did) {
|
|
14290
14441
|
lastPromptTokens = 0;
|
|
14291
14442
|
const reason = bytePressure ? `body ~${(bodyBytes / (1024 * 1024)).toFixed(1)}MB` : "context window";
|
|
14292
|
-
options.onText(
|
|
14293
|
-
\u267B\uFE0F Auto-compacted earlier conversation to stay within the ${reason}.
|
|
14294
|
-
`);
|
|
14443
|
+
(options.onNotice ?? options.onText)(`\u267B\uFE0F Auto-compacted earlier conversation to stay within the ${reason}.`);
|
|
14295
14444
|
}
|
|
14296
14445
|
} finally {
|
|
14297
14446
|
compacting = false;
|
|
@@ -14331,6 +14480,15 @@ Continue the work from here.` }] });
|
|
|
14331
14480
|
break;
|
|
14332
14481
|
case "message_complete":
|
|
14333
14482
|
break;
|
|
14483
|
+
case "retry":
|
|
14484
|
+
options.onRetry?.(event.attempt, event.maxAttempts, event.reason);
|
|
14485
|
+
break;
|
|
14486
|
+
case "retry_resolved":
|
|
14487
|
+
options.onRetryResolved?.();
|
|
14488
|
+
break;
|
|
14489
|
+
case "balance_status":
|
|
14490
|
+
options.onBalanceStatus?.(event.balance, event.zero);
|
|
14491
|
+
break;
|
|
14334
14492
|
case "done":
|
|
14335
14493
|
case "error":
|
|
14336
14494
|
break;
|
|
@@ -14353,6 +14511,12 @@ Continue the work from here.` }] });
|
|
|
14353
14511
|
} catch (err) {
|
|
14354
14512
|
if (options.abortSignal?.aborted || err.name === "AbortError")
|
|
14355
14513
|
break;
|
|
14514
|
+
const status = err.status;
|
|
14515
|
+
if (status === 402) {
|
|
14516
|
+
const balance = err.balance ?? 0;
|
|
14517
|
+
options.onBalanceStatus?.(balance, true);
|
|
14518
|
+
break;
|
|
14519
|
+
}
|
|
14356
14520
|
runSimpleHooks(hooks.OnError, options.workDir);
|
|
14357
14521
|
throw new Error(`Stream failed: ${err.message}`);
|
|
14358
14522
|
}
|
|
@@ -14371,9 +14535,12 @@ Continue the work from here.` }] });
|
|
|
14371
14535
|
completedCleanly = true;
|
|
14372
14536
|
break;
|
|
14373
14537
|
}
|
|
14374
|
-
|
|
14538
|
+
const { stopReason: _stopReason, ...historyMessage } = assistantMessage;
|
|
14539
|
+
messages.push(historyMessage);
|
|
14375
14540
|
const serverSideResultIds = new Set(assistantMessage.content.filter((b) => b.type === "tool_result").map((b) => b.tool_use_id).filter((id) => !!id));
|
|
14376
14541
|
const toolUseBlocks = assistantMessage.content.filter((block) => block.type === "tool_use" && !serverSideResultIds.has(block.id));
|
|
14542
|
+
const lastBlock = assistantMessage.content[assistantMessage.content.length - 1];
|
|
14543
|
+
const truncatedToolUseId = assistantMessage.stopReason === "max_tokens" && lastBlock?.type === "tool_use" ? lastBlock.id : void 0;
|
|
14377
14544
|
if (toolUseBlocks.length === 0) {
|
|
14378
14545
|
const queued = options.takePendingInput?.() ?? [];
|
|
14379
14546
|
if (queued.length) {
|
|
@@ -14441,8 +14608,15 @@ Continue the work from here.` }] });
|
|
|
14441
14608
|
const toolResults = await Promise.all(toolUseBlocks.map(async (block) => {
|
|
14442
14609
|
const { id, name, input } = block;
|
|
14443
14610
|
options.onToolUse(name, input);
|
|
14444
|
-
const description = humanDescription(name, input);
|
|
14445
14611
|
let result;
|
|
14612
|
+
if (id === truncatedToolUseId) {
|
|
14613
|
+
result = {
|
|
14614
|
+
error: `This tool call (${name}) was CUT OFF because the response hit the model's output-token limit mid-generation (stop_reason: max_tokens) \u2014 its arguments may be incomplete or missing fields entirely, so it was NOT executed to avoid a silent partial edit. Retry with a SMALLER call: ` + (name === "multi_edit" || name === "edit_file" ? "split this into fewer edits per call (or call edit_file once per change instead of one large multi_edit), " : name === "write_file" ? "write the file in smaller chunks via write_file + edit_file follow-ups instead of one large write_file, " : "") + `so the full response fits comfortably under the per-turn output budget.`
|
|
14615
|
+
};
|
|
14616
|
+
options.onToolResult(name, result);
|
|
14617
|
+
return { block: { ...block, id }, result };
|
|
14618
|
+
}
|
|
14619
|
+
const description = humanDescription(name, input);
|
|
14446
14620
|
let permitted;
|
|
14447
14621
|
try {
|
|
14448
14622
|
permitted = await options.requestPermission({ tool: name, input, description });
|
|
@@ -22883,11 +23057,15 @@ var import_code_core2 = __toESM(require_dist2());
|
|
|
22883
23057
|
var autoApproved = /* @__PURE__ */ new Set();
|
|
22884
23058
|
var _rules = { allow: [], ask: [], deny: [] };
|
|
22885
23059
|
var _workDir = process.cwd();
|
|
23060
|
+
var _mode = "auto";
|
|
22886
23061
|
function initPermissions(workDir) {
|
|
22887
23062
|
_workDir = workDir;
|
|
22888
23063
|
_rules = (0, import_code_core2.loadSettings)(workDir).permissions;
|
|
22889
23064
|
return _rules;
|
|
22890
23065
|
}
|
|
23066
|
+
function setMode(mode) {
|
|
23067
|
+
_mode = mode || "auto";
|
|
23068
|
+
}
|
|
22891
23069
|
function setAutoApprove(category) {
|
|
22892
23070
|
autoApproved.add(category);
|
|
22893
23071
|
}
|
|
@@ -22955,6 +23133,10 @@ async function requestPermission(req) {
|
|
|
22955
23133
|
];
|
|
22956
23134
|
if (readOnlyTools.includes(tool))
|
|
22957
23135
|
return true;
|
|
23136
|
+
if (_mode === "plan") {
|
|
23137
|
+
console.error(source_default.yellow(` \u2298 Plan mode \u2014 refused ${tool} (read-only until you switch mode).`));
|
|
23138
|
+
return false;
|
|
23139
|
+
}
|
|
22958
23140
|
if (tool === "write_file" || tool === "create_file") {
|
|
22959
23141
|
if (isAutoApproved("write"))
|
|
22960
23142
|
return true;
|
|
@@ -23356,6 +23538,23 @@ function tryExec(cmd, cwd) {
|
|
|
23356
23538
|
return void 0;
|
|
23357
23539
|
}
|
|
23358
23540
|
}
|
|
23541
|
+
var BILLING_URL = "https://app.nexrall.com/?settings=billing";
|
|
23542
|
+
function hyperlink(label, url) {
|
|
23543
|
+
return `\x1B]8;;${url}\x1B\\${label}\x1B]8;;\x1B\\`;
|
|
23544
|
+
}
|
|
23545
|
+
var lastBalanceNoticeState = null;
|
|
23546
|
+
function printBalanceNotice(balance, zero) {
|
|
23547
|
+
const state = zero ? "zero" : "low";
|
|
23548
|
+
if (lastBalanceNoticeState === state)
|
|
23549
|
+
return;
|
|
23550
|
+
lastBalanceNoticeState = state;
|
|
23551
|
+
const title = zero ? "Out of balance" : "Balance running low";
|
|
23552
|
+
const detail = zero ? "You're out of funds \u2014 top up to keep the agent working." : `Your wallet is under $5${typeof balance === "number" ? ` ($${balance.toFixed(2)} left)` : ""}. Top up before it runs out.`;
|
|
23553
|
+
console.log();
|
|
23554
|
+
console.log(source_default.bgYellow.black(` ${title} `) + " " + source_default.dim(detail));
|
|
23555
|
+
console.log(" " + source_default.yellow(hyperlink("\u2192 Top up", BILLING_URL)) + source_default.dim(` (${BILLING_URL})`));
|
|
23556
|
+
console.log();
|
|
23557
|
+
}
|
|
23359
23558
|
function collectEnv(workDir) {
|
|
23360
23559
|
return {
|
|
23361
23560
|
cwd: workDir,
|
|
@@ -23430,6 +23629,7 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env2, nexrall
|
|
|
23430
23629
|
let toolStartTime = 0;
|
|
23431
23630
|
let lastToolName = "";
|
|
23432
23631
|
let thinkingTokens = 0;
|
|
23632
|
+
setMode(mode);
|
|
23433
23633
|
process.stdout.write("\n");
|
|
23434
23634
|
const result = await (0, import_code_core3.runAgentLoop)(messages, {
|
|
23435
23635
|
workDir,
|
|
@@ -23456,6 +23656,18 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env2, nexrall
|
|
|
23456
23656
|
spinner.stop();
|
|
23457
23657
|
mdRender.feed(text);
|
|
23458
23658
|
},
|
|
23659
|
+
// System notices (mid-run auto-prune/auto-compact housekeeping) are NOT part
|
|
23660
|
+
// of the model's own reply — used to go through onText, which spliced
|
|
23661
|
+
// "♻️ Trimmed ~0.3MB…" straight into the markdown stream renderer as if the
|
|
23662
|
+
// model itself had said it. Print it as its own dim line instead (same
|
|
23663
|
+
// treatment as the resume-time compaction notice below).
|
|
23664
|
+
onNotice: (text) => {
|
|
23665
|
+
if (abortSignal.aborted)
|
|
23666
|
+
return;
|
|
23667
|
+
mdRender.flush();
|
|
23668
|
+
spinner.stop();
|
|
23669
|
+
console.log(source_default.dim(` ${text}`));
|
|
23670
|
+
},
|
|
23459
23671
|
onToolUse: (name, input) => {
|
|
23460
23672
|
if (abortSignal.aborted)
|
|
23461
23673
|
return;
|
|
@@ -23476,6 +23688,23 @@ async function runTurn(messages, modelAlias, workDir, abortSignal, env2, nexrall
|
|
|
23476
23688
|
onUsage: (u) => {
|
|
23477
23689
|
lastUsage = u;
|
|
23478
23690
|
},
|
|
23691
|
+
// A transient disconnect (network drop, machine sleep/wake, overloaded upstream)
|
|
23692
|
+
// is retried transparently by the network layer — without this, that pause was
|
|
23693
|
+
// completely invisible: the CLI just appeared to freeze and then resume with no
|
|
23694
|
+
// explanation. Reuse the same spinner to show what's actually happening.
|
|
23695
|
+
onRetry: (attempt, _maxAttempts, reason) => {
|
|
23696
|
+
if (abortSignal.aborted)
|
|
23697
|
+
return;
|
|
23698
|
+
spinner.start(`${reason}\u2026 (attempt ${attempt})`);
|
|
23699
|
+
},
|
|
23700
|
+
onRetryResolved: () => {
|
|
23701
|
+
spinner.stop();
|
|
23702
|
+
},
|
|
23703
|
+
onBalanceStatus: (balance, zero) => {
|
|
23704
|
+
spinner.stop();
|
|
23705
|
+
mdRender.flush();
|
|
23706
|
+
printBalanceNotice(balance, zero);
|
|
23707
|
+
},
|
|
23479
23708
|
requestPermission,
|
|
23480
23709
|
checkpointManager,
|
|
23481
23710
|
onProgress
|
|
@@ -23508,6 +23737,13 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env2
|
|
|
23508
23737
|
resultText += text;
|
|
23509
23738
|
emit({ type: "text", text });
|
|
23510
23739
|
},
|
|
23740
|
+
// System notice (mid-run auto-prune/auto-compact) — emit as its own event
|
|
23741
|
+
// type instead of falling through to onText, so a stream-json consumer
|
|
23742
|
+
// doesn't see compaction housekeeping text mixed into the model's `text`
|
|
23743
|
+
// events or accumulated into resultText.
|
|
23744
|
+
onNotice: (text) => {
|
|
23745
|
+
emit({ type: "notice", text });
|
|
23746
|
+
},
|
|
23511
23747
|
onToolUse: (name, input) => {
|
|
23512
23748
|
emit({ type: "tool_use", tool: name, input });
|
|
23513
23749
|
},
|
|
@@ -23519,6 +23755,12 @@ async function runTurnHeadless(messages, modelAlias, workDir, _abortSignal, env2
|
|
|
23519
23755
|
lastUsage = u;
|
|
23520
23756
|
emit({ type: "usage", usage: u });
|
|
23521
23757
|
},
|
|
23758
|
+
onRetry: (attempt, maxAttempts, reason) => {
|
|
23759
|
+
emit({ type: "retry", attempt, max_attempts: maxAttempts, reason });
|
|
23760
|
+
},
|
|
23761
|
+
onBalanceStatus: (balance, zero) => {
|
|
23762
|
+
emit({ type: "balance_status", balance, zero, billing_url: BILLING_URL });
|
|
23763
|
+
},
|
|
23522
23764
|
// Headless → auto-approve (no TTY to ask on). Destructive/irreversible
|
|
23523
23765
|
// commands (DB drops, force-push, terraform destroy…) fail CLOSED here: with
|
|
23524
23766
|
// no human to confirm, they are denied unless NEXRALL_ALLOW_DESTRUCTIVE=1 is
|
|
@@ -23642,9 +23884,23 @@ async function startChatSession(options) {
|
|
|
23642
23884
|
messages = stored.messages;
|
|
23643
23885
|
sessionId = stored.id;
|
|
23644
23886
|
sessionTitle = stored.title;
|
|
23645
|
-
|
|
23646
|
-
|
|
23647
|
-
|
|
23887
|
+
try {
|
|
23888
|
+
const compacted = await (0, import_code_core3.compactMessagesForResume)(messages, {
|
|
23889
|
+
workDir,
|
|
23890
|
+
model: modelAlias,
|
|
23891
|
+
clientType: "cli",
|
|
23892
|
+
onNotice: (text) => {
|
|
23893
|
+
if (!headless)
|
|
23894
|
+
console.log(source_default.dim(text.trim()));
|
|
23895
|
+
}
|
|
23896
|
+
});
|
|
23897
|
+
} catch {
|
|
23898
|
+
}
|
|
23899
|
+
if (!headless) {
|
|
23900
|
+
console.log(source_default.green(` Resumed session: ${source_default.bold(stored.title.slice(0, 60))}`));
|
|
23901
|
+
console.log(source_default.dim(` ${messages.length} messages restored`));
|
|
23902
|
+
console.log();
|
|
23903
|
+
}
|
|
23648
23904
|
}
|
|
23649
23905
|
const checkpoints = new import_code_core3.CheckpointManager(workDir, sessionId);
|
|
23650
23906
|
const updateTitle = () => {
|
|
@@ -23789,6 +24045,15 @@ ${text}` : "");
|
|
|
23789
24045
|
messages = stored.messages;
|
|
23790
24046
|
sessionId = stored.id;
|
|
23791
24047
|
sessionTitle = stored.title;
|
|
24048
|
+
try {
|
|
24049
|
+
await (0, import_code_core3.compactMessagesForResume)(messages, {
|
|
24050
|
+
workDir,
|
|
24051
|
+
model: modelAlias,
|
|
24052
|
+
clientType: "cli",
|
|
24053
|
+
onNotice: (text) => console.log(source_default.dim(text.trim()))
|
|
24054
|
+
});
|
|
24055
|
+
} catch {
|
|
24056
|
+
}
|
|
23792
24057
|
console.log(source_default.green(` Resumed: ${source_default.bold(stored.title.slice(0, 60))}`));
|
|
23793
24058
|
console.log(source_default.dim(` ${messages.length} messages restored`));
|
|
23794
24059
|
rl.prompt();
|
|
@@ -24335,7 +24600,7 @@ function pluginListCommand() {
|
|
|
24335
24600
|
|
|
24336
24601
|
// src/index.ts
|
|
24337
24602
|
var program2 = new Command();
|
|
24338
|
-
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.
|
|
24603
|
+
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.12");
|
|
24339
24604
|
program2.command("auth").description("Login to your Nexrall account").action(authCommand);
|
|
24340
24605
|
program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
|
|
24341
24606
|
program2.command("update").description("Update nex to the latest version").option("-c, --check", "Check for updates without installing").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexrall-code",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.12",
|
|
4
4
|
"description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"ora": "^8.0.1",
|
|
38
38
|
"prompts": "^2.4.2",
|
|
39
39
|
"readline": "^1.3.0",
|
|
40
|
-
"@nexrall/code-core": "1.4.
|
|
40
|
+
"@nexrall/code-core": "1.4.11"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@aws-sdk/client-s3": "^3.600.0",
|