@nexrall/code-core 1.4.14 → 1.4.15
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/api/client.d.ts.map +1 -1
- package/dist/api/client.js +77 -0
- package/package.json +1 -1
package/dist/api/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAA8B,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAiErH,eAAO,MAAM,QAAQ,QAAmB,CAAC;AAIzC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5D,OAAO,EAAE,CAAC,EAAE,EACZ,UAAU,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,GAAG,SAAS,GACtD,CAAC,EAAE,CAeL;AAeD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,aAAa,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACnC,oFAAoF;IACpF,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IACjG,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6IAA6I;IAC7I,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;;;OAeG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAiDD,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,GAC7B,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAA8B,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAiErH,eAAO,MAAM,QAAQ,QAAmB,CAAC;AAIzC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5D,OAAO,EAAE,CAAC,EAAE,EACZ,UAAU,EAAE,KAAK,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,GAAG,SAAS,GACtD,CAAC,EAAE,CAeL;AAeD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,aAAa,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACnC,oFAAoF;IACpF,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IACjG,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6IAA6I;IAC7I,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;;;OAeG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAiDD,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,GAC7B,OAAO,CAAC,OAAO,CAAC,CA05BlB;AAID;;;;;;;;;;;GAWG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAU9D;AAID,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAalD;AAID,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAgB1E;AAID,wBAAsB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAkBhF"}
|
package/dist/api/client.js
CHANGED
|
@@ -328,6 +328,36 @@ async function streamChat(messages, options, onEvent) {
|
|
|
328
328
|
await sleepWithinBudget(backoffMs(attempt));
|
|
329
329
|
continue;
|
|
330
330
|
}
|
|
331
|
+
// 403 `{"error":{"type":"forbidden","message":"Request not allowed"}}` is
|
|
332
|
+
// Anthropic's own upstream response shape (reproduced verbatim through the AI
|
|
333
|
+
// Gateway passthrough — see services/aiGatewayClient.js), not something Nexrall's
|
|
334
|
+
// auth layer produces (an expired/invalid Nexrall JWT is a 401/403 with a
|
|
335
|
+
// completely different body — see middleware/auth.js). Reports of this exact
|
|
336
|
+
// error on Anthropic's own CLI describe it as intermittent and gone on retry, not
|
|
337
|
+
// a real permissions failure — so unlike every OTHER 4xx below (which are
|
|
338
|
+
// deterministic given the same body and must not be retried), this one gets a
|
|
339
|
+
// short, capped number of attempts before being treated as terminal. Capped well
|
|
340
|
+
// below MAX_RETRIES so a genuinely revoked/banned key still fails fast instead of
|
|
341
|
+
// burning the whole reconnect budget on something that will never succeed.
|
|
342
|
+
const FORBIDDEN_RETRY_LIMIT = 2;
|
|
343
|
+
if (response.status === 403 && attempt < FORBIDDEN_RETRY_LIMIT && canRetry()) {
|
|
344
|
+
const bodyText = await response.text().catch(() => '');
|
|
345
|
+
let isUpstreamForbidden = false;
|
|
346
|
+
try {
|
|
347
|
+
const parsed = JSON.parse(bodyText);
|
|
348
|
+
isUpstreamForbidden = parsed?.error?.type === 'forbidden';
|
|
349
|
+
}
|
|
350
|
+
catch { /* not JSON — not this shape, fall through as terminal below */ }
|
|
351
|
+
if (isUpstreamForbidden) {
|
|
352
|
+
reportRetry('Upstream rejected the request — retrying');
|
|
353
|
+
await sleepWithinBudget(backoffMs(attempt));
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
// Not the known transient shape — treat as terminal, but reuse the body we
|
|
357
|
+
// already consumed (node-fetch bodies are single-use) instead of losing it.
|
|
358
|
+
errorBodyOverride = bodyText;
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
331
361
|
// 410 Gone — a resume was refused because the buffered window no longer contains
|
|
332
362
|
// our position (we were away too long, or the turn produced a huge amount in the
|
|
333
363
|
// meantime). Continuity can't be proven, and silently skipping frames would corrupt
|
|
@@ -841,6 +871,16 @@ async function streamChat(messages, options, onEvent) {
|
|
|
841
871
|
case 'error': {
|
|
842
872
|
const message = typeof evt.message === 'string' ? evt.message :
|
|
843
873
|
typeof evt.error === 'string' ? evt.error : 'Unknown SSE error';
|
|
874
|
+
// The GET /chat/resume tail loop sends this when the turn's Redis frame
|
|
875
|
+
// buffer developed a gap (trimmed window, a swallowed flush error, or the
|
|
876
|
+
// owning pod died) or the turn is simply gone — see routes/code.js. Unlike
|
|
877
|
+
// `isRetryableStreamMsg` below, this is NOT a fuzzy text match: the backend
|
|
878
|
+
// sets the flag explicitly, so it can never silently stop matching if either
|
|
879
|
+
// side rewrites the English message. There is no cursor left to resume
|
|
880
|
+
// from — a plain "retryable" resume-with-Last-Event-ID would just 410 again
|
|
881
|
+
// — so this always needs a fresh turnId, which the outer loop mints via
|
|
882
|
+
// `forceRestart`.
|
|
883
|
+
const notResumable = evt.notResumable === true;
|
|
844
884
|
// Transient upstream failure before any output → let the outer loop retry it
|
|
845
885
|
// transparently instead of killing the turn (this is the "Overloaded" case).
|
|
846
886
|
// But never retry once the turn's message has already been delivered in
|
|
@@ -852,6 +892,11 @@ async function streamChat(messages, options, onEvent) {
|
|
|
852
892
|
onEvent({ type: 'error', message });
|
|
853
893
|
resolve();
|
|
854
894
|
}
|
|
895
|
+
else if (notResumable && (!emittedToCaller || allowRestartAfterRender)) {
|
|
896
|
+
clearInterval(heartbeatWatchdog);
|
|
897
|
+
stream.destroy?.();
|
|
898
|
+
reject(Object.assign(tagTransient(new Error(message)), { forceRestart: true }));
|
|
899
|
+
}
|
|
855
900
|
else if (isRetryableStreamMsg(message) && (!emittedToCaller || allowRestartAfterRender)) {
|
|
856
901
|
clearInterval(heartbeatWatchdog);
|
|
857
902
|
stream.destroy?.();
|
|
@@ -939,6 +984,38 @@ async function streamChat(messages, options, onEvent) {
|
|
|
939
984
|
throw err;
|
|
940
985
|
const e = err;
|
|
941
986
|
if (e.retryable && sAttempt < MAX_RETRIES && canRetry()) {
|
|
987
|
+
// The backend explicitly said this turnId's buffer is unrecoverable (gap in the
|
|
988
|
+
// Redis frame stream, or the owning pod is gone — see the `notResumable` case in
|
|
989
|
+
// client.ts's SSE parser). Resuming with the same `lastEventId` would just hit
|
|
990
|
+
// the same wall again, so — exactly like the inline 410-while-resuming handling
|
|
991
|
+
// above — release the old claim, mint a fresh turnId, and restart clean instead
|
|
992
|
+
// of falling into the "prefer resuming" branch below.
|
|
993
|
+
if (e.forceRestart) {
|
|
994
|
+
await cancelTurn(turnId);
|
|
995
|
+
turnId = (0, crypto_1.randomUUID)();
|
|
996
|
+
resuming = false;
|
|
997
|
+
serverResumable = false;
|
|
998
|
+
lastEventId = 0;
|
|
999
|
+
carryText = [];
|
|
1000
|
+
carryToolUse = [];
|
|
1001
|
+
const hadRendered = emittedAnythingAcrossAttempts;
|
|
1002
|
+
const renderedChars = emittedCharsAcrossAttempts;
|
|
1003
|
+
emittedAnythingAcrossAttempts = false;
|
|
1004
|
+
emittedCharsAcrossAttempts = 0;
|
|
1005
|
+
if (hadRendered && !allowRestartAfterRender) {
|
|
1006
|
+
throw new Error('Connection lost and this turn could no longer be resumed. Send your message again.');
|
|
1007
|
+
}
|
|
1008
|
+
if (hadRendered) {
|
|
1009
|
+
onEvent({
|
|
1010
|
+
type: 'stream_restart',
|
|
1011
|
+
reason: err.message || 'Could not resume — restarting this turn',
|
|
1012
|
+
discardedChars: renderedChars,
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
reportRetry('Could not resume — restarting this turn');
|
|
1016
|
+
await sleepWithinBudget(backoffMs(sAttempt));
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
942
1019
|
// PREFER RESUMING over restarting.
|
|
943
1020
|
//
|
|
944
1021
|
// The turn is still generating on the server (a dropped socket no longer aborts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexrall/code-core",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.15",
|
|
4
4
|
"description": "Core agent loop, tools, and extension primitives for Nexrall Code — embed an AI coding agent in any Node.js application.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nexrall <support@nexrall.com> (https://nexrall.com)",
|