nexrall-code 0.5.51 → 0.5.52
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 +288 -228
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -10234,248 +10234,254 @@ var require_client = __commonJS({
|
|
|
10234
10234
|
};
|
|
10235
10235
|
const haveCompleteMessage = () => completedMessage !== null;
|
|
10236
10236
|
const partialInputs = {};
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
}
|
|
10253
|
-
if (now - lastDataAt > HEARTBEAT_TIMEOUT_MS) {
|
|
10254
|
-
clearInterval(heartbeatWatchdog);
|
|
10255
|
-
stream.destroy?.();
|
|
10256
|
-
const recoverable = !emittedToCaller || !!allowRestartAfterRender;
|
|
10257
|
-
const secs = Math.round(HEARTBEAT_TIMEOUT_MS / 1e3);
|
|
10258
|
-
reject(tagTransient(new Error(recoverable ? `Connection lost \u2014 no data received for ${secs} s. Reconnecting\u2026` : `Connection lost \u2014 no data received for ${secs} s. Retry your message.`)));
|
|
10259
|
-
return;
|
|
10260
|
-
}
|
|
10261
|
-
const stallLimitMs = sawModelEvent ? PROGRESS_TIMEOUT_MS : FIRST_EVENT_TIMEOUT_MS;
|
|
10262
|
-
if (now - lastProgressAt > stallLimitMs) {
|
|
10263
|
-
clearInterval(heartbeatWatchdog);
|
|
10264
|
-
stream.destroy?.();
|
|
10265
|
-
if (haveCompleteMessage()) {
|
|
10237
|
+
let heartbeatWatchdog;
|
|
10238
|
+
try {
|
|
10239
|
+
await new Promise((resolve3, reject) => {
|
|
10240
|
+
const stream = response.body;
|
|
10241
|
+
const HEARTBEAT_TIMEOUT_MS = 9e4;
|
|
10242
|
+
const FIRST_EVENT_TIMEOUT_MS = Number(process.env.NEXRALL_FIRST_EVENT_TIMEOUT_MS) || 3e5;
|
|
10243
|
+
const PROGRESS_TIMEOUT_MS = Number(process.env.NEXRALL_PROGRESS_TIMEOUT_MS) || 15e4;
|
|
10244
|
+
let sawModelEvent = false;
|
|
10245
|
+
let lastDataAt = Date.now();
|
|
10246
|
+
let lastProgressAt = Date.now();
|
|
10247
|
+
heartbeatWatchdog = setInterval(() => {
|
|
10248
|
+
const now = Date.now();
|
|
10249
|
+
if (haveCompleteMessage() && now - lastDataAt > HEARTBEAT_TIMEOUT_MS) {
|
|
10250
|
+
clearInterval(heartbeatWatchdog);
|
|
10251
|
+
stream.destroy?.();
|
|
10266
10252
|
resolve3();
|
|
10267
10253
|
return;
|
|
10268
10254
|
}
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
if (event.type !== "event") {
|
|
10276
|
-
return;
|
|
10277
|
-
}
|
|
10278
|
-
{
|
|
10279
|
-
const frameId = event.id;
|
|
10280
|
-
if (frameId) {
|
|
10281
|
-
const n = Number(frameId);
|
|
10282
|
-
if (Number.isFinite(n) && n > lastEventId)
|
|
10283
|
-
lastEventId = n;
|
|
10284
|
-
}
|
|
10285
|
-
const raw = event.data;
|
|
10286
|
-
if (!raw || raw === "[DONE]") {
|
|
10287
|
-
resolve3();
|
|
10255
|
+
if (now - lastDataAt > HEARTBEAT_TIMEOUT_MS) {
|
|
10256
|
+
clearInterval(heartbeatWatchdog);
|
|
10257
|
+
stream.destroy?.();
|
|
10258
|
+
const recoverable = !emittedToCaller || !!allowRestartAfterRender;
|
|
10259
|
+
const secs = Math.round(HEARTBEAT_TIMEOUT_MS / 1e3);
|
|
10260
|
+
reject(tagTransient(new Error(recoverable ? `Connection lost \u2014 no data received for ${secs} s. Reconnecting\u2026` : `Connection lost \u2014 no data received for ${secs} s. Retry your message.`)));
|
|
10288
10261
|
return;
|
|
10289
10262
|
}
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10263
|
+
const stallLimitMs = sawModelEvent ? PROGRESS_TIMEOUT_MS : FIRST_EVENT_TIMEOUT_MS;
|
|
10264
|
+
if (now - lastProgressAt > stallLimitMs) {
|
|
10265
|
+
clearInterval(heartbeatWatchdog);
|
|
10266
|
+
stream.destroy?.();
|
|
10267
|
+
if (haveCompleteMessage()) {
|
|
10268
|
+
resolve3();
|
|
10269
|
+
return;
|
|
10270
|
+
}
|
|
10271
|
+
reject(tagTransient(new Error(sawModelEvent ? `The model stopped responding mid-stream (no output for ${Math.round(stallLimitMs / 1e3)} s).` : `The model did not start responding within ${Math.round(stallLimitMs / 1e3)} s (large context can take a while to process).`)));
|
|
10294
10272
|
return;
|
|
10295
10273
|
}
|
|
10296
|
-
|
|
10274
|
+
}, 5e3);
|
|
10275
|
+
const parser = (0, eventsource_parser_1.createParser)((event) => {
|
|
10276
|
+
lastDataAt = Date.now();
|
|
10277
|
+
if (event.type !== "event") {
|
|
10297
10278
|
return;
|
|
10298
10279
|
}
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
const text = typeof evt.text === "string" ? evt.text : "";
|
|
10306
|
-
textParts.push(text);
|
|
10307
|
-
emittedToCaller = true;
|
|
10308
|
-
emittedChars += text.length;
|
|
10309
|
-
emittedAnythingAcrossAttempts = true;
|
|
10310
|
-
emittedCharsAcrossAttempts += text.length;
|
|
10311
|
-
onEvent({ type: "text", text });
|
|
10312
|
-
break;
|
|
10280
|
+
{
|
|
10281
|
+
const frameId = event.id;
|
|
10282
|
+
if (frameId) {
|
|
10283
|
+
const n = Number(frameId);
|
|
10284
|
+
if (Number.isFinite(n) && n > lastEventId)
|
|
10285
|
+
lastEventId = n;
|
|
10313
10286
|
}
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
name: typeof evt.name === "string" ? evt.name : "",
|
|
10319
|
-
input: evt.input ?? {}
|
|
10320
|
-
};
|
|
10321
|
-
if (typeof evt.input_json_delta === "string") {
|
|
10322
|
-
partialInputs[block.id] = (partialInputs[block.id] ?? "") + evt.input_json_delta;
|
|
10323
|
-
try {
|
|
10324
|
-
block.input = JSON.parse(partialInputs[block.id]);
|
|
10325
|
-
} catch {
|
|
10326
|
-
return;
|
|
10327
|
-
}
|
|
10328
|
-
}
|
|
10329
|
-
toolUseBlocks.push(block);
|
|
10330
|
-
emittedToCaller = true;
|
|
10331
|
-
emittedAnythingAcrossAttempts = true;
|
|
10332
|
-
onEvent({ type: "tool_use", id: block.id, name: block.name, input: block.input });
|
|
10333
|
-
break;
|
|
10334
|
-
}
|
|
10335
|
-
case "message_complete": {
|
|
10336
|
-
const nestedMessage = evt.message;
|
|
10337
|
-
const stopReason = typeof nestedMessage?.stop_reason === "string" ? nestedMessage.stop_reason : null;
|
|
10338
|
-
const contentBlocks2 = [];
|
|
10339
|
-
const fullText2 = textParts.join("");
|
|
10340
|
-
if (fullText2) {
|
|
10341
|
-
contentBlocks2.push({ type: "text", text: fullText2 });
|
|
10342
|
-
}
|
|
10343
|
-
contentBlocks2.push(...toolUseBlocks);
|
|
10344
|
-
const rawContent = Array.isArray(nestedMessage?.content) ? nestedMessage.content : null;
|
|
10345
|
-
const finalContent = chooseFinalContent(contentBlocks2, rawContent);
|
|
10346
|
-
completedMessage = {
|
|
10347
|
-
role: "assistant",
|
|
10348
|
-
content: finalContent,
|
|
10349
|
-
stopReason
|
|
10350
|
-
};
|
|
10351
|
-
onEvent({ type: "message_complete", message: completedMessage });
|
|
10352
|
-
break;
|
|
10287
|
+
const raw = event.data;
|
|
10288
|
+
if (!raw || raw === "[DONE]") {
|
|
10289
|
+
resolve3();
|
|
10290
|
+
return;
|
|
10353
10291
|
}
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
// billed but never completed (its stream `abort` path). Without this
|
|
10360
|
-
// the flag dies here and every consumer that sums usage silently folds
|
|
10361
|
-
// a discarded attempt's tokens into the successful turn's total.
|
|
10362
|
-
...evt.partial === true ? { partial: true } : {},
|
|
10363
|
-
// `replayed` means these tokens are being reported a SECOND time: the
|
|
10364
|
-
// turn completed and was billed on an earlier attempt whose `done` never
|
|
10365
|
-
// reached us, and the backend served this one from its idempotency cache
|
|
10366
|
-
// instead of re-running the model. Nothing new was charged, so a cost
|
|
10367
|
-
// display must not add them again.
|
|
10368
|
-
...evt.replayed === true ? { replayed: true } : {},
|
|
10369
|
-
usage: {
|
|
10370
|
-
input_tokens: evt.input_tokens,
|
|
10371
|
-
output_tokens: evt.output_tokens,
|
|
10372
|
-
cache_creation_input_tokens: typeof evt.cache_creation_input_tokens === "number" ? evt.cache_creation_input_tokens : void 0,
|
|
10373
|
-
cache_read_input_tokens: typeof evt.cache_read_input_tokens === "number" ? evt.cache_read_input_tokens : void 0
|
|
10374
|
-
}
|
|
10375
|
-
});
|
|
10376
|
-
}
|
|
10377
|
-
break;
|
|
10292
|
+
let parsed;
|
|
10293
|
+
try {
|
|
10294
|
+
parsed = JSON.parse(raw);
|
|
10295
|
+
} catch {
|
|
10296
|
+
return;
|
|
10378
10297
|
}
|
|
10379
|
-
|
|
10380
|
-
|
|
10298
|
+
if (typeof parsed !== "object" || parsed === null || !("type" in parsed)) {
|
|
10299
|
+
return;
|
|
10381
10300
|
}
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10301
|
+
const evt = parsed;
|
|
10302
|
+
lastProgressAt = Date.now();
|
|
10303
|
+
sawModelEvent = true;
|
|
10304
|
+
clearRetryIfNeeded();
|
|
10305
|
+
switch (evt.type) {
|
|
10306
|
+
case "text": {
|
|
10307
|
+
const text = typeof evt.text === "string" ? evt.text : "";
|
|
10308
|
+
textParts.push(text);
|
|
10385
10309
|
emittedToCaller = true;
|
|
10386
10310
|
emittedChars += text.length;
|
|
10387
10311
|
emittedAnythingAcrossAttempts = true;
|
|
10388
10312
|
emittedCharsAcrossAttempts += text.length;
|
|
10389
|
-
onEvent({ type: "
|
|
10313
|
+
onEvent({ type: "text", text });
|
|
10314
|
+
break;
|
|
10390
10315
|
}
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
|
|
10316
|
+
case "tool_use": {
|
|
10317
|
+
const block = {
|
|
10318
|
+
type: "tool_use",
|
|
10319
|
+
id: typeof evt.id === "string" ? evt.id : "",
|
|
10320
|
+
name: typeof evt.name === "string" ? evt.name : "",
|
|
10321
|
+
input: evt.input ?? {}
|
|
10322
|
+
};
|
|
10323
|
+
if (typeof evt.input_json_delta === "string") {
|
|
10324
|
+
partialInputs[block.id] = (partialInputs[block.id] ?? "") + evt.input_json_delta;
|
|
10325
|
+
try {
|
|
10326
|
+
block.input = JSON.parse(partialInputs[block.id]);
|
|
10327
|
+
} catch {
|
|
10328
|
+
return;
|
|
10329
|
+
}
|
|
10330
|
+
}
|
|
10331
|
+
toolUseBlocks.push(block);
|
|
10401
10332
|
emittedToCaller = true;
|
|
10402
|
-
emittedChars += text.length;
|
|
10403
10333
|
emittedAnythingAcrossAttempts = true;
|
|
10404
|
-
|
|
10405
|
-
|
|
10334
|
+
onEvent({ type: "tool_use", id: block.id, name: block.name, input: block.input });
|
|
10335
|
+
break;
|
|
10406
10336
|
}
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
|
|
10420
|
-
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
|
|
10337
|
+
case "message_complete": {
|
|
10338
|
+
const nestedMessage = evt.message;
|
|
10339
|
+
const stopReason = typeof nestedMessage?.stop_reason === "string" ? nestedMessage.stop_reason : null;
|
|
10340
|
+
const contentBlocks2 = [];
|
|
10341
|
+
const fullText2 = textParts.join("");
|
|
10342
|
+
if (fullText2) {
|
|
10343
|
+
contentBlocks2.push({ type: "text", text: fullText2 });
|
|
10344
|
+
}
|
|
10345
|
+
contentBlocks2.push(...toolUseBlocks);
|
|
10346
|
+
const rawContent = Array.isArray(nestedMessage?.content) ? nestedMessage.content : null;
|
|
10347
|
+
const finalContent = chooseFinalContent(contentBlocks2, rawContent);
|
|
10348
|
+
completedMessage = {
|
|
10349
|
+
role: "assistant",
|
|
10350
|
+
content: finalContent,
|
|
10351
|
+
stopReason
|
|
10352
|
+
};
|
|
10353
|
+
onEvent({ type: "message_complete", message: completedMessage });
|
|
10354
|
+
break;
|
|
10355
|
+
}
|
|
10356
|
+
case "usage": {
|
|
10357
|
+
if (typeof evt.input_tokens === "number" && typeof evt.output_tokens === "number") {
|
|
10358
|
+
onEvent({
|
|
10359
|
+
type: "usage",
|
|
10360
|
+
// Forwarded, not dropped: the backend tags the usage of an attempt it
|
|
10361
|
+
// billed but never completed (its stream `abort` path). Without this
|
|
10362
|
+
// the flag dies here and every consumer that sums usage silently folds
|
|
10363
|
+
// a discarded attempt's tokens into the successful turn's total.
|
|
10364
|
+
...evt.partial === true ? { partial: true } : {},
|
|
10365
|
+
// `replayed` means these tokens are being reported a SECOND time: the
|
|
10366
|
+
// turn completed and was billed on an earlier attempt whose `done` never
|
|
10367
|
+
// reached us, and the backend served this one from its idempotency cache
|
|
10368
|
+
// instead of re-running the model. Nothing new was charged, so a cost
|
|
10369
|
+
// display must not add them again.
|
|
10370
|
+
...evt.replayed === true ? { replayed: true } : {},
|
|
10371
|
+
usage: {
|
|
10372
|
+
input_tokens: evt.input_tokens,
|
|
10373
|
+
output_tokens: evt.output_tokens,
|
|
10374
|
+
cache_creation_input_tokens: typeof evt.cache_creation_input_tokens === "number" ? evt.cache_creation_input_tokens : void 0,
|
|
10375
|
+
cache_read_input_tokens: typeof evt.cache_read_input_tokens === "number" ? evt.cache_read_input_tokens : void 0
|
|
10376
|
+
}
|
|
10377
|
+
});
|
|
10378
|
+
}
|
|
10379
|
+
break;
|
|
10380
|
+
}
|
|
10381
|
+
case "context_warning": {
|
|
10382
|
+
break;
|
|
10383
|
+
}
|
|
10384
|
+
case "thinking": {
|
|
10385
|
+
const text = typeof evt.text === "string" ? evt.text : "";
|
|
10386
|
+
if (text) {
|
|
10387
|
+
emittedToCaller = true;
|
|
10388
|
+
emittedChars += text.length;
|
|
10389
|
+
emittedAnythingAcrossAttempts = true;
|
|
10390
|
+
emittedCharsAcrossAttempts += text.length;
|
|
10391
|
+
onEvent({ type: "thinking", text });
|
|
10392
|
+
}
|
|
10393
|
+
break;
|
|
10394
|
+
}
|
|
10395
|
+
case "thinking_progress": {
|
|
10396
|
+
const tokens = typeof evt.tokens === "number" ? evt.tokens : 0;
|
|
10397
|
+
onEvent({ type: "thinking_progress", tokens });
|
|
10398
|
+
break;
|
|
10399
|
+
}
|
|
10400
|
+
case "thinking_delta": {
|
|
10401
|
+
const text = typeof evt.text === "string" ? evt.text : "";
|
|
10402
|
+
if (text) {
|
|
10403
|
+
emittedToCaller = true;
|
|
10404
|
+
emittedChars += text.length;
|
|
10405
|
+
emittedAnythingAcrossAttempts = true;
|
|
10406
|
+
emittedCharsAcrossAttempts += text.length;
|
|
10407
|
+
onEvent({ type: "thinking_delta", text });
|
|
10408
|
+
}
|
|
10409
|
+
break;
|
|
10410
|
+
}
|
|
10411
|
+
case "resumable": {
|
|
10412
|
+
serverResumable = true;
|
|
10413
|
+
break;
|
|
10414
|
+
}
|
|
10415
|
+
case "balance_status": {
|
|
10416
|
+
const balance = typeof evt.balance === "number" ? evt.balance : 0;
|
|
10417
|
+
const zero = !!evt.zero;
|
|
10418
|
+
onEvent({ type: "balance_status", balance, zero });
|
|
10419
|
+
break;
|
|
10420
|
+
}
|
|
10421
|
+
case "done": {
|
|
10422
|
+
onEvent({ type: "done" });
|
|
10430
10423
|
resolve3();
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10424
|
+
break;
|
|
10425
|
+
}
|
|
10426
|
+
case "error": {
|
|
10427
|
+
const message = typeof evt.message === "string" ? evt.message : typeof evt.error === "string" ? evt.error : "Unknown SSE error";
|
|
10428
|
+
const notResumable = evt.notResumable === true;
|
|
10429
|
+
if (haveCompleteMessage()) {
|
|
10430
|
+
clearInterval(heartbeatWatchdog);
|
|
10431
|
+
onEvent({ type: "error", message });
|
|
10432
|
+
resolve3();
|
|
10433
|
+
} else if (notResumable && (!emittedToCaller || allowRestartAfterRender)) {
|
|
10434
|
+
clearInterval(heartbeatWatchdog);
|
|
10435
|
+
stream.destroy?.();
|
|
10436
|
+
reject(Object.assign(tagTransient(new Error(message)), { forceRestart: true }));
|
|
10437
|
+
} else if (isRetryableStreamMsg(message) && (!emittedToCaller || allowRestartAfterRender)) {
|
|
10438
|
+
clearInterval(heartbeatWatchdog);
|
|
10439
|
+
stream.destroy?.();
|
|
10440
|
+
reject(tagTransient(new Error(message)));
|
|
10441
|
+
} else {
|
|
10442
|
+
clearInterval(heartbeatWatchdog);
|
|
10443
|
+
stream.destroy?.();
|
|
10444
|
+
onEvent({ type: "error", message });
|
|
10445
|
+
reject(new Error(message));
|
|
10446
|
+
}
|
|
10447
|
+
break;
|
|
10444
10448
|
}
|
|
10445
|
-
break;
|
|
10446
10449
|
}
|
|
10447
10450
|
}
|
|
10448
|
-
}
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
}
|
|
10464
|
-
resolve3();
|
|
10465
|
-
});
|
|
10466
|
-
stream.on("error", (err) => {
|
|
10467
|
-
clearInterval(heartbeatWatchdog);
|
|
10468
|
-
if (abortSignal?.aborted || controller.signal.aborted) {
|
|
10469
|
-
resolve3();
|
|
10470
|
-
return;
|
|
10471
|
-
}
|
|
10472
|
-
if (haveCompleteMessage()) {
|
|
10451
|
+
});
|
|
10452
|
+
stream.on("data", (chunk) => {
|
|
10453
|
+
lastDataAt = Date.now();
|
|
10454
|
+
if (abortSignal?.aborted) {
|
|
10455
|
+
stream.destroy?.();
|
|
10456
|
+
return;
|
|
10457
|
+
}
|
|
10458
|
+
parser.feed(chunk.toString("utf-8"));
|
|
10459
|
+
});
|
|
10460
|
+
stream.on("end", () => {
|
|
10461
|
+
clearInterval(heartbeatWatchdog);
|
|
10462
|
+
if (!sawModelEvent && !completedMessage) {
|
|
10463
|
+
reject(Object.assign(new Error("Connection closed before the model responded. Retrying\u2026"), { retryable: true }));
|
|
10464
|
+
return;
|
|
10465
|
+
}
|
|
10473
10466
|
resolve3();
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10467
|
+
});
|
|
10468
|
+
stream.on("error", (err) => {
|
|
10469
|
+
clearInterval(heartbeatWatchdog);
|
|
10470
|
+
if (abortSignal?.aborted || controller.signal.aborted) {
|
|
10471
|
+
resolve3();
|
|
10472
|
+
return;
|
|
10473
|
+
}
|
|
10474
|
+
if (haveCompleteMessage()) {
|
|
10475
|
+
resolve3();
|
|
10476
|
+
return;
|
|
10477
|
+
}
|
|
10478
|
+
reject(tagTransient(err));
|
|
10479
|
+
});
|
|
10477
10480
|
});
|
|
10478
|
-
}
|
|
10481
|
+
} finally {
|
|
10482
|
+
if (heartbeatWatchdog !== void 0)
|
|
10483
|
+
clearInterval(heartbeatWatchdog);
|
|
10484
|
+
}
|
|
10479
10485
|
if (completedMessage) {
|
|
10480
10486
|
return completedMessage;
|
|
10481
10487
|
}
|
|
@@ -15757,7 +15763,8 @@ var require_loop = __commonJS({
|
|
|
15757
15763
|
};
|
|
15758
15764
|
}();
|
|
15759
15765
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15760
|
-
exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = void 0;
|
|
15766
|
+
exports.VERIFY_CMD_RE = exports.WRITE_TOOL_NAMES = exports.ToolNotAllowedError = exports._stallLimits = void 0;
|
|
15767
|
+
exports.errorRoundSignature = errorRoundSignature;
|
|
15761
15768
|
exports.resolveMaxIterations = resolveMaxIterations;
|
|
15762
15769
|
exports.createLimiter = createLimiter;
|
|
15763
15770
|
exports.extractSubTaskText = extractSubTaskText;
|
|
@@ -15890,6 +15897,11 @@ var require_loop = __commonJS({
|
|
|
15890
15897
|
var MAX_ITERATIONS_CEILING = 2e3;
|
|
15891
15898
|
var HARD_ITERATIONS_CAP = 1e5;
|
|
15892
15899
|
var STALL_LIMIT = 8;
|
|
15900
|
+
var REPEAT_STALL_LIMIT = 12;
|
|
15901
|
+
function errorRoundSignature(errored) {
|
|
15902
|
+
return errored.map(({ name, error }) => `${name}:${String(error).slice(0, 200)}`).sort().join("|");
|
|
15903
|
+
}
|
|
15904
|
+
exports._stallLimits = { STALL_LIMIT, REPEAT_STALL_LIMIT };
|
|
15893
15905
|
function resolveMaxIterations(optionValue, settingsRaw) {
|
|
15894
15906
|
const fromEnv = Number(process.env.NEXRALL_MAX_ITERATIONS);
|
|
15895
15907
|
const fromSettings = Number(settingsRaw.maxIterations);
|
|
@@ -16266,6 +16278,8 @@ ${partial}` : "",
|
|
|
16266
16278
|
}
|
|
16267
16279
|
var PRUNE_MIN_RECLAIM_BYTES = 256 * 1024;
|
|
16268
16280
|
var COMPACT_KEEP_MIN = 6;
|
|
16281
|
+
var COMPACT_MIN_RECLAIM_BYTES = 32 * 1024;
|
|
16282
|
+
var COMPACT_MAX_FAILURES = 3;
|
|
16269
16283
|
var MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
16270
16284
|
function estimateBodyBytes2(messages) {
|
|
16271
16285
|
try {
|
|
@@ -16347,7 +16361,7 @@ ${tail}`;
|
|
|
16347
16361
|
var LEDGER_MAX_FILES = 60;
|
|
16348
16362
|
var LEDGER_MAX_NOTES = 20;
|
|
16349
16363
|
function createLedger() {
|
|
16350
|
-
return { filesTouched: /* @__PURE__ */ new Map(), verifications: [], testIntegrity: [], epoch: 0 };
|
|
16364
|
+
return { filesTouched: /* @__PURE__ */ new Map(), filesTouchedTotal: 0, verifications: [], testIntegrity: [], testIntegrityTotal: 0, epoch: 0 };
|
|
16351
16365
|
}
|
|
16352
16366
|
function ledgerRecord(ledger, toolName, input, ok, output, exitCode) {
|
|
16353
16367
|
if (exports.WRITE_TOOL_NAMES.has(toolName)) {
|
|
@@ -16357,7 +16371,18 @@ ${tail}`;
|
|
|
16357
16371
|
const p = typeof input?.path === "string" ? input.path : void 0;
|
|
16358
16372
|
if (p) {
|
|
16359
16373
|
const prev = ledger.filesTouched.get(p);
|
|
16374
|
+
if (!prev)
|
|
16375
|
+
ledger.filesTouchedTotal++;
|
|
16376
|
+
ledger.filesTouched.delete(p);
|
|
16360
16377
|
ledger.filesTouched.set(p, { tool: toolName, edits: (prev?.edits ?? 0) + 1 });
|
|
16378
|
+
if (ledger.filesTouched.size > LEDGER_MAX_FILES * 4) {
|
|
16379
|
+
for (const key of ledger.filesTouched.keys()) {
|
|
16380
|
+
if (ledger.filesTouched.size <= LEDGER_MAX_FILES * 2)
|
|
16381
|
+
break;
|
|
16382
|
+
if (key !== p)
|
|
16383
|
+
ledger.filesTouched.delete(key);
|
|
16384
|
+
}
|
|
16385
|
+
}
|
|
16361
16386
|
}
|
|
16362
16387
|
const reasons = [];
|
|
16363
16388
|
const markerReasons = toolName === "write_file" ? (0, testIntegrity_1.decodeTestIntegrityMarker)(output) : [];
|
|
@@ -16371,6 +16396,7 @@ ${tail}`;
|
|
|
16371
16396
|
if (reasons.length && p) {
|
|
16372
16397
|
for (const reason of reasons) {
|
|
16373
16398
|
ledger.testIntegrity.push({ path: p, reason });
|
|
16399
|
+
ledger.testIntegrityTotal++;
|
|
16374
16400
|
}
|
|
16375
16401
|
if (ledger.testIntegrity.length > LEDGER_MAX_NOTES * 2) {
|
|
16376
16402
|
ledger.testIntegrity.splice(0, ledger.testIntegrity.length - LEDGER_MAX_NOTES);
|
|
@@ -16391,8 +16417,8 @@ ${tail}`;
|
|
|
16391
16417
|
const lines = [];
|
|
16392
16418
|
if (ledger.filesTouched.size) {
|
|
16393
16419
|
const files = [...ledger.filesTouched.entries()];
|
|
16394
|
-
const shown = files.slice(
|
|
16395
|
-
lines.push(`FILES CHANGED THIS SESSION (${ledger.filesTouched.size}):`);
|
|
16420
|
+
const shown = files.slice(-LEDGER_MAX_FILES);
|
|
16421
|
+
lines.push(`FILES CHANGED THIS SESSION (${ledger.filesTouchedTotal || ledger.filesTouched.size}):`);
|
|
16396
16422
|
for (const [p, meta] of shown) {
|
|
16397
16423
|
lines.push(` \u2022 ${p} (${meta.tool}${meta.edits > 1 ? ` \xD7${meta.edits}` : ""})`);
|
|
16398
16424
|
}
|
|
@@ -16629,6 +16655,9 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16629
16655
|
let completedRounds = 0;
|
|
16630
16656
|
let stalledOut = false;
|
|
16631
16657
|
let consecutiveErrorRounds = 0;
|
|
16658
|
+
let repeatedErrorRounds = 0;
|
|
16659
|
+
let lastErrorSignature = "";
|
|
16660
|
+
let stalledRepeatError = null;
|
|
16632
16661
|
let budget = maxIterations;
|
|
16633
16662
|
let iteration = 0;
|
|
16634
16663
|
let filesMutatedSinceVerify = false;
|
|
@@ -16638,6 +16667,8 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16638
16667
|
const flakyNudgedCmds = /* @__PURE__ */ new Set();
|
|
16639
16668
|
let claimEvidenceNudged = false;
|
|
16640
16669
|
const ledger = createLedger();
|
|
16670
|
+
let compactFailures = 0;
|
|
16671
|
+
let compactDisabled = false;
|
|
16641
16672
|
try {
|
|
16642
16673
|
for (; iteration < budget; iteration++) {
|
|
16643
16674
|
if (options.abortSignal?.aborted)
|
|
@@ -16654,14 +16685,25 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16654
16685
|
(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.`);
|
|
16655
16686
|
}
|
|
16656
16687
|
}
|
|
16657
|
-
if (autoCompact && !compacting && (tokenPressure || bytePressure) && messages.length > COMPACT_KEEP_MIN + 2) {
|
|
16688
|
+
if (autoCompact && !compactDisabled && !compacting && (tokenPressure || bytePressure) && messages.length > COMPACT_KEEP_MIN + 2) {
|
|
16658
16689
|
compacting = true;
|
|
16659
16690
|
try {
|
|
16691
|
+
const bytesBefore = estimateBodyBytes2(messages);
|
|
16660
16692
|
const did = await autoCompactMessages(messages, options, ledger);
|
|
16693
|
+
const bytesAfter = did ? estimateBodyBytes2(messages) : bytesBefore;
|
|
16694
|
+
const reclaimed = bytesBefore - bytesAfter;
|
|
16661
16695
|
if (did) {
|
|
16662
16696
|
lastPromptTokens = 0;
|
|
16663
16697
|
const reason = bytePressure ? `body ~${(bodyBytes / (1024 * 1024)).toFixed(1)}MB` : "context window";
|
|
16664
16698
|
(options.onNotice ?? options.onText)(`\u267B\uFE0F Auto-compacted earlier conversation to stay within the ${reason}.`);
|
|
16699
|
+
bodyBytes = bytesAfter;
|
|
16700
|
+
bytePressure = bodyBytes > MAX_BODY_BYTES;
|
|
16701
|
+
}
|
|
16702
|
+
if (did && reclaimed >= COMPACT_MIN_RECLAIM_BYTES) {
|
|
16703
|
+
compactFailures = 0;
|
|
16704
|
+
} else if (++compactFailures >= COMPACT_MAX_FAILURES) {
|
|
16705
|
+
compactDisabled = true;
|
|
16706
|
+
(options.onNotice ?? options.onText)(`\u26A0\uFE0F Auto-compaction isn't reducing this conversation any further, so it's been switched off for the rest of this run to avoid repeated summarising. If the context fills up, start a fresh chat or run /compact manually.`);
|
|
16665
16707
|
}
|
|
16666
16708
|
} finally {
|
|
16667
16709
|
compacting = false;
|
|
@@ -16776,9 +16818,10 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16776
16818
|
messages.push({ role: "user", content: [{ type: "text", text }] });
|
|
16777
16819
|
continue;
|
|
16778
16820
|
}
|
|
16779
|
-
if (ledger.
|
|
16780
|
-
const
|
|
16781
|
-
|
|
16821
|
+
if (ledger.testIntegrityTotal > testIntegrityNudgedCount) {
|
|
16822
|
+
const newCount = Math.min(ledger.testIntegrityTotal - testIntegrityNudgedCount, ledger.testIntegrity.length);
|
|
16823
|
+
const fresh = ledger.testIntegrity.slice(ledger.testIntegrity.length - newCount);
|
|
16824
|
+
testIntegrityNudgedCount = ledger.testIntegrityTotal;
|
|
16782
16825
|
const bullet = fresh.map((t2) => ` \u2022 ${t2.path}: ${t2.reason}`).join("\n");
|
|
16783
16826
|
messages.push({
|
|
16784
16827
|
role: "user",
|
|
@@ -16959,12 +17002,25 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
|
|
|
16959
17002
|
messages.push(toolResultMessage);
|
|
16960
17003
|
completedRounds++;
|
|
16961
17004
|
options.onProgress?.(messages);
|
|
16962
|
-
const
|
|
17005
|
+
const errored = toolResults.filter(({ result }) => result.error !== void 0);
|
|
17006
|
+
const allErrored = toolResults.length > 0 && errored.length === toolResults.length;
|
|
16963
17007
|
consecutiveErrorRounds = allErrored ? consecutiveErrorRounds + 1 : 0;
|
|
16964
17008
|
if (consecutiveErrorRounds >= STALL_LIMIT) {
|
|
16965
17009
|
stalledOut = true;
|
|
16966
17010
|
break;
|
|
16967
17011
|
}
|
|
17012
|
+
const errSignature = errorRoundSignature(errored.map(({ block, result }) => ({ name: block.name, error: String(result.error) })));
|
|
17013
|
+
if (errSignature && errSignature === lastErrorSignature) {
|
|
17014
|
+
repeatedErrorRounds++;
|
|
17015
|
+
} else {
|
|
17016
|
+
repeatedErrorRounds = 0;
|
|
17017
|
+
lastErrorSignature = errSignature;
|
|
17018
|
+
}
|
|
17019
|
+
if (repeatedErrorRounds >= REPEAT_STALL_LIMIT) {
|
|
17020
|
+
stalledOut = true;
|
|
17021
|
+
stalledRepeatError = errored[0] ? String(errored[0].result.error).slice(0, 300) : null;
|
|
17022
|
+
break;
|
|
17023
|
+
}
|
|
16968
17024
|
if (autoContinue && iteration + 1 >= budget && budget < hardCap) {
|
|
16969
17025
|
budget = Math.min(budget + maxIterations, hardCap);
|
|
16970
17026
|
options.onText(`
|
|
@@ -16974,11 +17030,15 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
|
|
|
16974
17030
|
}
|
|
16975
17031
|
if (!options.abortSignal?.aborted && !completedCleanly) {
|
|
16976
17032
|
if (stalledOut) {
|
|
16977
|
-
options.onText(`
|
|
17033
|
+
(options.onNotice ?? options.onText)(stalledRepeatError ? `
|
|
17034
|
+
\u{1F6D1} Stopped: the same tool error repeated ${REPEAT_STALL_LIMIT} rounds in a row, so the agent was looping without making progress. The recurring error was:
|
|
17035
|
+
${stalledRepeatError}
|
|
17036
|
+
Fix that underlying cause (or grant the needed permission) and send "continue".
|
|
17037
|
+
` : `
|
|
16978
17038
|
\u{1F6D1} Stopped: the last ${STALL_LIMIT} tool rounds all failed, so the agent looked stuck. Fix the underlying error (or grant the needed permission) and send "continue".
|
|
16979
17039
|
`);
|
|
16980
17040
|
} else if (iteration >= budget) {
|
|
16981
|
-
options.onText(`
|
|
17041
|
+
(options.onNotice ?? options.onText)(`
|
|
16982
17042
|
\u23F8\uFE0F Stopped at the ${budget}-step safety limit \u2014 the task may be incomplete. Send "continue" to resume, or raise the limit via "maxIterations" in .nexrall/settings.json (or the NEXRALL_MAX_ITERATIONS env var). Auto-continue can be disabled with "autoContinue": false.
|
|
16983
17043
|
`);
|
|
16984
17044
|
}
|
|
@@ -63944,7 +64004,7 @@ var InkReadlineAdapter = class extends EventEmitter3 {
|
|
|
63944
64004
|
};
|
|
63945
64005
|
|
|
63946
64006
|
// src/commands/chat.ts
|
|
63947
|
-
var CLI_VERSION = "0.5.
|
|
64007
|
+
var CLI_VERSION = "0.5.52";
|
|
63948
64008
|
var MODEL_LABELS = {
|
|
63949
64009
|
turbo: "Nexrall Turbo",
|
|
63950
64010
|
pro: "Nexrall Pro",
|
|
@@ -65611,7 +65671,7 @@ function pluginSourceRemoveCommand(name, opts) {
|
|
|
65611
65671
|
|
|
65612
65672
|
// src/index.ts
|
|
65613
65673
|
var program2 = new Command();
|
|
65614
|
-
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.
|
|
65674
|
+
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.52").enablePositionalOptions();
|
|
65615
65675
|
program2.command("auth").description("Login to your Nexrall account").action(authCommand);
|
|
65616
65676
|
program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
|
|
65617
65677
|
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.52",
|
|
4
4
|
"description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"react": "^19.2.8",
|
|
42
42
|
"readline": "^1.3.0",
|
|
43
43
|
"string-width": "^7.2.0",
|
|
44
|
-
"@nexrall/code-core": "1.4.
|
|
44
|
+
"@nexrall/code-core": "1.4.27"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@aws-sdk/client-s3": "^3.600.0",
|