@nexrall/code-core 1.4.15 → 1.4.16

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.
@@ -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,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"}
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,CAi7BlB;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"}
@@ -314,6 +314,10 @@ async function streamChat(messages, options, onEvent) {
314
314
  // A 409's body, already consumed to classify it. node-fetch bodies are single-use, so
315
315
  // the !response.ok handler below reads this instead of calling response.text() again.
316
316
  let errorBodyOverride = null;
317
+ // Set when a 403 upstream-forbidden (Anthropic passthrough, see below) used up its
318
+ // short in-loop retry budget — the final throw tags `retryable: true` so it still
319
+ // reaches the outer 5-minute stream-retry loop instead of dying as a terminal error.
320
+ let forbiddenExhausted = false;
317
321
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
318
322
  try {
319
323
  response = await (0, node_fetch_1.default)(...buildFetchArgs());
@@ -340,7 +344,7 @@ async function streamChat(messages, options, onEvent) {
340
344
  // below MAX_RETRIES so a genuinely revoked/banned key still fails fast instead of
341
345
  // burning the whole reconnect budget on something that will never succeed.
342
346
  const FORBIDDEN_RETRY_LIMIT = 2;
343
- if (response.status === 403 && attempt < FORBIDDEN_RETRY_LIMIT && canRetry()) {
347
+ if (response.status === 403) {
344
348
  const bodyText = await response.text().catch(() => '');
345
349
  let isUpstreamForbidden = false;
346
350
  try {
@@ -349,9 +353,21 @@ async function streamChat(messages, options, onEvent) {
349
353
  }
350
354
  catch { /* not JSON — not this shape, fall through as terminal below */ }
351
355
  if (isUpstreamForbidden) {
352
- reportRetry('Upstream rejected the request retrying');
353
- await sleepWithinBudget(backoffMs(attempt));
354
- continue;
356
+ if (attempt < FORBIDDEN_RETRY_LIMIT && canRetry()) {
357
+ reportRetry('Upstream rejected the request — retrying');
358
+ await sleepWithinBudget(backoffMs(attempt));
359
+ continue;
360
+ }
361
+ // Exhausted the short in-connect-loop attempt budget, but this is still the
362
+ // SAME transient upstream shape (not a real permissions failure) — it must not
363
+ // die here after only ~2-4s of backoff. `forbiddenExhausted` makes the final
364
+ // throw below tag `retryable: true` so the OUTER stream-retry loop (which owns
365
+ // the shared 5-minute reconnect budget — see MAX_TOTAL_RETRY_MS) picks it back
366
+ // up and keeps trying, instead of surfacing a bare terminal "API error 403"
367
+ // while minutes of retry budget were still available.
368
+ forbiddenExhausted = true;
369
+ errorBodyOverride = bodyText;
370
+ break;
355
371
  }
356
372
  // Not the known transient shape — treat as terminal, but reuse the body we
357
373
  // already consumed (node-fetch bodies are single-use) instead of losing it.
@@ -498,7 +514,11 @@ async function streamChat(messages, options, onEvent) {
498
514
  // Tag a 402 (pre-flight "wallet already empty" gate — see routes/code.js) so the
499
515
  // agent loop can distinguish it from a generic error and surface the low/zero-
500
516
  // balance topup nudge immediately, instead of a plain "API error 402" message.
501
- throw Object.assign(new Error(errMsg), { status: response.status, balance });
517
+ //
518
+ // `retryable: true` on `forbiddenExhausted` hands the transient 403-upstream-forbidden
519
+ // case (see above) to the outer stream-retry loop instead of letting it die as a
520
+ // terminal error the moment the short in-connect-loop budget runs out.
521
+ throw Object.assign(new Error(errMsg), { status: response.status, balance, ...(forbiddenExhausted ? { retryable: true } : {}) });
502
522
  }
503
523
  if (!response.body) {
504
524
  throw new Error('Response body is null');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexrall/code-core",
3
- "version": "1.4.15",
3
+ "version": "1.4.16",
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)",