nexrall-code 0.5.51 → 0.5.53
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 +339 -245
- 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;
|
|
10313
|
-
}
|
|
10314
|
-
case "tool_use": {
|
|
10315
|
-
const block = {
|
|
10316
|
-
type: "tool_use",
|
|
10317
|
-
id: typeof evt.id === "string" ? evt.id : "",
|
|
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;
|
|
10280
|
+
{
|
|
10281
|
+
const frameId = event.id;
|
|
10282
|
+
if (frameId) {
|
|
10283
|
+
const n = Number(frameId);
|
|
10284
|
+
if (Number.isFinite(n) && n > lastEventId)
|
|
10285
|
+
lastEventId = n;
|
|
10334
10286
|
}
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
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,9 @@ 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;
|
|
15768
|
+
exports.stopReasonNotice = stopReasonNotice;
|
|
15761
15769
|
exports.resolveMaxIterations = resolveMaxIterations;
|
|
15762
15770
|
exports.createLimiter = createLimiter;
|
|
15763
15771
|
exports.extractSubTaskText = extractSubTaskText;
|
|
@@ -15890,6 +15898,46 @@ var require_loop = __commonJS({
|
|
|
15890
15898
|
var MAX_ITERATIONS_CEILING = 2e3;
|
|
15891
15899
|
var HARD_ITERATIONS_CAP = 1e5;
|
|
15892
15900
|
var STALL_LIMIT = 8;
|
|
15901
|
+
var REPEAT_STALL_LIMIT = 12;
|
|
15902
|
+
function errorRoundSignature(errored) {
|
|
15903
|
+
return errored.map(({ name, error }) => `${name}:${String(error).slice(0, 200)}`).sort().join("|");
|
|
15904
|
+
}
|
|
15905
|
+
exports._stallLimits = { STALL_LIMIT, REPEAT_STALL_LIMIT };
|
|
15906
|
+
function stopReasonNotice(reason, ctx = {}) {
|
|
15907
|
+
switch (reason) {
|
|
15908
|
+
case "clean":
|
|
15909
|
+
case "aborted":
|
|
15910
|
+
case "reported-elsewhere":
|
|
15911
|
+
return null;
|
|
15912
|
+
case "empty-response":
|
|
15913
|
+
return `
|
|
15914
|
+
\u26A0\uFE0F The model returned an empty response, so nothing was done. This is usually a transient upstream hiccup \u2014 send "continue" to retry.
|
|
15915
|
+
`;
|
|
15916
|
+
case "no-balance":
|
|
15917
|
+
return `
|
|
15918
|
+
\u{1F4B3} Stopped: your balance is empty, so the request was rejected before it started. Top up and send "continue" \u2014 no tokens were used for this turn.
|
|
15919
|
+
`;
|
|
15920
|
+
case "stalled":
|
|
15921
|
+
return `
|
|
15922
|
+
\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".
|
|
15923
|
+
`;
|
|
15924
|
+
case "stalled-repeat":
|
|
15925
|
+
return `
|
|
15926
|
+
\u{1F6D1} Stopped: the same tool error repeated ${REPEAT_STALL_LIMIT} rounds in a row, so the agent was looping without making progress.` + (ctx.repeatError ? ` The recurring error was:
|
|
15927
|
+
${ctx.repeatError}
|
|
15928
|
+
` : "\n") + `Fix that underlying cause (or grant the needed permission) and send "continue".
|
|
15929
|
+
`;
|
|
15930
|
+
case "budget":
|
|
15931
|
+
return `
|
|
15932
|
+
\u23F8\uFE0F Stopped at the ${ctx.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.
|
|
15933
|
+
`;
|
|
15934
|
+
case "unknown":
|
|
15935
|
+
default:
|
|
15936
|
+
return `
|
|
15937
|
+
\u26A0\uFE0F The run ended unexpectedly without completing. Your work so far is preserved \u2014 send "continue" to resume.
|
|
15938
|
+
`;
|
|
15939
|
+
}
|
|
15940
|
+
}
|
|
15893
15941
|
function resolveMaxIterations(optionValue, settingsRaw) {
|
|
15894
15942
|
const fromEnv = Number(process.env.NEXRALL_MAX_ITERATIONS);
|
|
15895
15943
|
const fromSettings = Number(settingsRaw.maxIterations);
|
|
@@ -16266,6 +16314,8 @@ ${partial}` : "",
|
|
|
16266
16314
|
}
|
|
16267
16315
|
var PRUNE_MIN_RECLAIM_BYTES = 256 * 1024;
|
|
16268
16316
|
var COMPACT_KEEP_MIN = 6;
|
|
16317
|
+
var COMPACT_MIN_RECLAIM_BYTES = 32 * 1024;
|
|
16318
|
+
var COMPACT_MAX_FAILURES = 3;
|
|
16269
16319
|
var MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
16270
16320
|
function estimateBodyBytes2(messages) {
|
|
16271
16321
|
try {
|
|
@@ -16347,7 +16397,7 @@ ${tail}`;
|
|
|
16347
16397
|
var LEDGER_MAX_FILES = 60;
|
|
16348
16398
|
var LEDGER_MAX_NOTES = 20;
|
|
16349
16399
|
function createLedger() {
|
|
16350
|
-
return { filesTouched: /* @__PURE__ */ new Map(), verifications: [], testIntegrity: [], epoch: 0 };
|
|
16400
|
+
return { filesTouched: /* @__PURE__ */ new Map(), filesTouchedTotal: 0, verifications: [], testIntegrity: [], testIntegrityTotal: 0, epoch: 0 };
|
|
16351
16401
|
}
|
|
16352
16402
|
function ledgerRecord(ledger, toolName, input, ok, output, exitCode) {
|
|
16353
16403
|
if (exports.WRITE_TOOL_NAMES.has(toolName)) {
|
|
@@ -16357,7 +16407,18 @@ ${tail}`;
|
|
|
16357
16407
|
const p = typeof input?.path === "string" ? input.path : void 0;
|
|
16358
16408
|
if (p) {
|
|
16359
16409
|
const prev = ledger.filesTouched.get(p);
|
|
16410
|
+
if (!prev)
|
|
16411
|
+
ledger.filesTouchedTotal++;
|
|
16412
|
+
ledger.filesTouched.delete(p);
|
|
16360
16413
|
ledger.filesTouched.set(p, { tool: toolName, edits: (prev?.edits ?? 0) + 1 });
|
|
16414
|
+
if (ledger.filesTouched.size > LEDGER_MAX_FILES * 4) {
|
|
16415
|
+
for (const key of ledger.filesTouched.keys()) {
|
|
16416
|
+
if (ledger.filesTouched.size <= LEDGER_MAX_FILES * 2)
|
|
16417
|
+
break;
|
|
16418
|
+
if (key !== p)
|
|
16419
|
+
ledger.filesTouched.delete(key);
|
|
16420
|
+
}
|
|
16421
|
+
}
|
|
16361
16422
|
}
|
|
16362
16423
|
const reasons = [];
|
|
16363
16424
|
const markerReasons = toolName === "write_file" ? (0, testIntegrity_1.decodeTestIntegrityMarker)(output) : [];
|
|
@@ -16371,6 +16432,7 @@ ${tail}`;
|
|
|
16371
16432
|
if (reasons.length && p) {
|
|
16372
16433
|
for (const reason of reasons) {
|
|
16373
16434
|
ledger.testIntegrity.push({ path: p, reason });
|
|
16435
|
+
ledger.testIntegrityTotal++;
|
|
16374
16436
|
}
|
|
16375
16437
|
if (ledger.testIntegrity.length > LEDGER_MAX_NOTES * 2) {
|
|
16376
16438
|
ledger.testIntegrity.splice(0, ledger.testIntegrity.length - LEDGER_MAX_NOTES);
|
|
@@ -16391,8 +16453,8 @@ ${tail}`;
|
|
|
16391
16453
|
const lines = [];
|
|
16392
16454
|
if (ledger.filesTouched.size) {
|
|
16393
16455
|
const files = [...ledger.filesTouched.entries()];
|
|
16394
|
-
const shown = files.slice(
|
|
16395
|
-
lines.push(`FILES CHANGED THIS SESSION (${ledger.filesTouched.size}):`);
|
|
16456
|
+
const shown = files.slice(-LEDGER_MAX_FILES);
|
|
16457
|
+
lines.push(`FILES CHANGED THIS SESSION (${ledger.filesTouchedTotal || ledger.filesTouched.size}):`);
|
|
16396
16458
|
for (const [p, meta] of shown) {
|
|
16397
16459
|
lines.push(` \u2022 ${p} (${meta.tool}${meta.edits > 1 ? ` \xD7${meta.edits}` : ""})`);
|
|
16398
16460
|
}
|
|
@@ -16625,10 +16687,12 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16625
16687
|
let lastPromptTokens = 0;
|
|
16626
16688
|
let compacting = false;
|
|
16627
16689
|
const hardCap = autoContinue ? Math.max(maxIterations, MAX_ITERATIONS_CEILING) : maxIterations;
|
|
16628
|
-
let
|
|
16690
|
+
let stopReason = "unknown";
|
|
16629
16691
|
let completedRounds = 0;
|
|
16630
|
-
let stalledOut = false;
|
|
16631
16692
|
let consecutiveErrorRounds = 0;
|
|
16693
|
+
let repeatedErrorRounds = 0;
|
|
16694
|
+
let lastErrorSignature = "";
|
|
16695
|
+
let stalledRepeatError = null;
|
|
16632
16696
|
let budget = maxIterations;
|
|
16633
16697
|
let iteration = 0;
|
|
16634
16698
|
let filesMutatedSinceVerify = false;
|
|
@@ -16638,10 +16702,14 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16638
16702
|
const flakyNudgedCmds = /* @__PURE__ */ new Set();
|
|
16639
16703
|
let claimEvidenceNudged = false;
|
|
16640
16704
|
const ledger = createLedger();
|
|
16705
|
+
let compactFailures = 0;
|
|
16706
|
+
let compactDisabled = false;
|
|
16641
16707
|
try {
|
|
16642
16708
|
for (; iteration < budget; iteration++) {
|
|
16643
|
-
if (options.abortSignal?.aborted)
|
|
16709
|
+
if (options.abortSignal?.aborted) {
|
|
16710
|
+
stopReason = "aborted";
|
|
16644
16711
|
break;
|
|
16712
|
+
}
|
|
16645
16713
|
let bodyBytes = estimateBodyBytes2(messages);
|
|
16646
16714
|
const prunePressure = lastPromptTokens > contextWindow * AUTO_PRUNE_THRESHOLD;
|
|
16647
16715
|
const tokenPressure = lastPromptTokens > contextWindow * AUTO_COMPACT_THRESHOLD;
|
|
@@ -16654,14 +16722,25 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16654
16722
|
(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
16723
|
}
|
|
16656
16724
|
}
|
|
16657
|
-
if (autoCompact && !compacting && (tokenPressure || bytePressure) && messages.length > COMPACT_KEEP_MIN + 2) {
|
|
16725
|
+
if (autoCompact && !compactDisabled && !compacting && (tokenPressure || bytePressure) && messages.length > COMPACT_KEEP_MIN + 2) {
|
|
16658
16726
|
compacting = true;
|
|
16659
16727
|
try {
|
|
16728
|
+
const bytesBefore = estimateBodyBytes2(messages);
|
|
16660
16729
|
const did = await autoCompactMessages(messages, options, ledger);
|
|
16730
|
+
const bytesAfter = did ? estimateBodyBytes2(messages) : bytesBefore;
|
|
16731
|
+
const reclaimed = bytesBefore - bytesAfter;
|
|
16661
16732
|
if (did) {
|
|
16662
16733
|
lastPromptTokens = 0;
|
|
16663
16734
|
const reason = bytePressure ? `body ~${(bodyBytes / (1024 * 1024)).toFixed(1)}MB` : "context window";
|
|
16664
16735
|
(options.onNotice ?? options.onText)(`\u267B\uFE0F Auto-compacted earlier conversation to stay within the ${reason}.`);
|
|
16736
|
+
bodyBytes = bytesAfter;
|
|
16737
|
+
bytePressure = bodyBytes > MAX_BODY_BYTES;
|
|
16738
|
+
}
|
|
16739
|
+
if (did && reclaimed >= COMPACT_MIN_RECLAIM_BYTES) {
|
|
16740
|
+
compactFailures = 0;
|
|
16741
|
+
} else if (++compactFailures >= COMPACT_MAX_FAILURES) {
|
|
16742
|
+
compactDisabled = true;
|
|
16743
|
+
(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
16744
|
}
|
|
16666
16745
|
} finally {
|
|
16667
16746
|
compacting = false;
|
|
@@ -16736,19 +16815,24 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16736
16815
|
allowRestartAfterRender: !!options.onStreamRestart
|
|
16737
16816
|
}, onEvent);
|
|
16738
16817
|
} catch (err) {
|
|
16739
|
-
if (options.abortSignal?.aborted || err.name === "AbortError")
|
|
16818
|
+
if (options.abortSignal?.aborted || err.name === "AbortError") {
|
|
16819
|
+
stopReason = "aborted";
|
|
16740
16820
|
break;
|
|
16821
|
+
}
|
|
16741
16822
|
const status = err.status;
|
|
16742
16823
|
if (status === 402) {
|
|
16743
16824
|
const balance = err.balance ?? 0;
|
|
16744
16825
|
options.onBalanceStatus?.(balance, true);
|
|
16826
|
+
stopReason = options.onBalanceStatus ? "reported-elsewhere" : "no-balance";
|
|
16745
16827
|
break;
|
|
16746
16828
|
}
|
|
16747
16829
|
runSimpleHooks(hooks.OnError, options.workDir);
|
|
16748
16830
|
throw new types_1.AgentTurnError(`Stream failed: ${err.message}`, trimToResumableBoundary(messages), completedRounds, err);
|
|
16749
16831
|
}
|
|
16750
|
-
if (options.abortSignal?.aborted)
|
|
16832
|
+
if (options.abortSignal?.aborted) {
|
|
16833
|
+
stopReason = "aborted";
|
|
16751
16834
|
break;
|
|
16835
|
+
}
|
|
16752
16836
|
assistantMessage.content = assistantMessage.content.filter((b) => b.type !== "thinking" && b.type !== "redacted_thinking");
|
|
16753
16837
|
if (assistantMessage.content.length === 0) {
|
|
16754
16838
|
const queued = options.takePendingInput?.() ?? [];
|
|
@@ -16759,7 +16843,7 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16759
16843
|
continue;
|
|
16760
16844
|
}
|
|
16761
16845
|
runSimpleHooks(hooks.PostMessageComplete, options.workDir);
|
|
16762
|
-
|
|
16846
|
+
stopReason = "empty-response";
|
|
16763
16847
|
break;
|
|
16764
16848
|
}
|
|
16765
16849
|
const { stopReason: _stopReason, ...historyMessage } = assistantMessage;
|
|
@@ -16776,9 +16860,10 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16776
16860
|
messages.push({ role: "user", content: [{ type: "text", text }] });
|
|
16777
16861
|
continue;
|
|
16778
16862
|
}
|
|
16779
|
-
if (ledger.
|
|
16780
|
-
const
|
|
16781
|
-
|
|
16863
|
+
if (ledger.testIntegrityTotal > testIntegrityNudgedCount) {
|
|
16864
|
+
const newCount = Math.min(ledger.testIntegrityTotal - testIntegrityNudgedCount, ledger.testIntegrity.length);
|
|
16865
|
+
const fresh = ledger.testIntegrity.slice(ledger.testIntegrity.length - newCount);
|
|
16866
|
+
testIntegrityNudgedCount = ledger.testIntegrityTotal;
|
|
16782
16867
|
const bullet = fresh.map((t2) => ` \u2022 ${t2.path}: ${t2.reason}`).join("\n");
|
|
16783
16868
|
messages.push({
|
|
16784
16869
|
role: "user",
|
|
@@ -16829,7 +16914,7 @@ ${options.nexrallMd}` : "") : options.nexrallMd;
|
|
|
16829
16914
|
}
|
|
16830
16915
|
}
|
|
16831
16916
|
runSimpleHooks(hooks.PostMessageComplete, options.workDir);
|
|
16832
|
-
|
|
16917
|
+
stopReason = "clean";
|
|
16833
16918
|
break;
|
|
16834
16919
|
}
|
|
16835
16920
|
const toolResults = await Promise.all(toolUseBlocks.map(async (block) => {
|
|
@@ -16959,10 +17044,23 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
|
|
|
16959
17044
|
messages.push(toolResultMessage);
|
|
16960
17045
|
completedRounds++;
|
|
16961
17046
|
options.onProgress?.(messages);
|
|
16962
|
-
const
|
|
17047
|
+
const errored = toolResults.filter(({ result }) => result.error !== void 0);
|
|
17048
|
+
const allErrored = toolResults.length > 0 && errored.length === toolResults.length;
|
|
16963
17049
|
consecutiveErrorRounds = allErrored ? consecutiveErrorRounds + 1 : 0;
|
|
16964
17050
|
if (consecutiveErrorRounds >= STALL_LIMIT) {
|
|
16965
|
-
|
|
17051
|
+
stopReason = "stalled";
|
|
17052
|
+
break;
|
|
17053
|
+
}
|
|
17054
|
+
const errSignature = errorRoundSignature(errored.map(({ block, result }) => ({ name: block.name, error: String(result.error) })));
|
|
17055
|
+
if (errSignature && errSignature === lastErrorSignature) {
|
|
17056
|
+
repeatedErrorRounds++;
|
|
17057
|
+
} else {
|
|
17058
|
+
repeatedErrorRounds = 0;
|
|
17059
|
+
lastErrorSignature = errSignature;
|
|
17060
|
+
}
|
|
17061
|
+
if (repeatedErrorRounds >= REPEAT_STALL_LIMIT) {
|
|
17062
|
+
stopReason = "stalled-repeat";
|
|
17063
|
+
stalledRepeatError = errored[0] ? String(errored[0].result.error).slice(0, 300) : null;
|
|
16966
17064
|
break;
|
|
16967
17065
|
}
|
|
16968
17066
|
if (autoContinue && iteration + 1 >= budget && budget < hardCap) {
|
|
@@ -16972,17 +17070,13 @@ ${result.output}` : `Error: ${result.error}` : result.output ?? "",
|
|
|
16972
17070
|
`);
|
|
16973
17071
|
}
|
|
16974
17072
|
}
|
|
16975
|
-
if (
|
|
16976
|
-
|
|
16977
|
-
|
|
16978
|
-
|
|
16979
|
-
|
|
16980
|
-
|
|
16981
|
-
|
|
16982
|
-
\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
|
-
`);
|
|
16984
|
-
}
|
|
16985
|
-
}
|
|
17073
|
+
if (stopReason === "unknown" && iteration >= budget)
|
|
17074
|
+
stopReason = "budget";
|
|
17075
|
+
if (options.abortSignal?.aborted)
|
|
17076
|
+
stopReason = "aborted";
|
|
17077
|
+
const notice = stopReasonNotice(stopReason, { budget, repeatError: stalledRepeatError });
|
|
17078
|
+
if (notice)
|
|
17079
|
+
(options.onNotice ?? options.onText)(notice);
|
|
16986
17080
|
} catch (err) {
|
|
16987
17081
|
if (err instanceof types_1.AgentTurnError)
|
|
16988
17082
|
throw err;
|
|
@@ -63944,7 +64038,7 @@ var InkReadlineAdapter = class extends EventEmitter3 {
|
|
|
63944
64038
|
};
|
|
63945
64039
|
|
|
63946
64040
|
// src/commands/chat.ts
|
|
63947
|
-
var CLI_VERSION = "0.5.
|
|
64041
|
+
var CLI_VERSION = "0.5.53";
|
|
63948
64042
|
var MODEL_LABELS = {
|
|
63949
64043
|
turbo: "Nexrall Turbo",
|
|
63950
64044
|
pro: "Nexrall Pro",
|
|
@@ -65611,7 +65705,7 @@ function pluginSourceRemoveCommand(name, opts) {
|
|
|
65611
65705
|
|
|
65612
65706
|
// src/index.ts
|
|
65613
65707
|
var program2 = new Command();
|
|
65614
|
-
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.
|
|
65708
|
+
program2.name("nex").description("Nexrall Code \u2014 AI coding assistant (powered by Nexrall)").version("0.5.53").enablePositionalOptions();
|
|
65615
65709
|
program2.command("auth").description("Login to your Nexrall account").action(authCommand);
|
|
65616
65710
|
program2.command("logout").description("Log out of your Nexrall account").action(logoutCommand);
|
|
65617
65711
|
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.53",
|
|
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.28"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@aws-sdk/client-s3": "^3.600.0",
|