@juspay/neurolink 11.24.2 → 11.25.1

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.
@@ -247,8 +247,14 @@ export class BaseProvider {
247
247
  return this.wrapStreamWithLifecycleCallbacks(realStreamResult, options);
248
248
  }
249
249
  catch (realStreamError) {
250
- // Don't retry on terminal/abort errors only fall back for
251
- // "real streaming with tools is unsupported" style failures.
250
+ // The fallback is BROAD, not narrow: only the terminal errors listed
251
+ // below (abort, timeout, 401/403, quota, rate limit, authentication)
252
+ // re-throw. Every other failure — including a genuine configuration or
253
+ // programming error — is masked as a degraded fake stream whenever
254
+ // tools are enabled. Narrowing this to "streaming with tools is
255
+ // unsupported" failures would change behaviour for every provider at
256
+ // once, so it needs its own characterization PR first; until then this
257
+ // comment records what the code does, not what a narrower design would.
252
258
  const errMsg = realStreamError instanceof Error
253
259
  ? realStreamError.message
254
260
  : String(realStreamError);
package/dist/neurolink.js CHANGED
@@ -7354,7 +7354,17 @@ Current user's request: ${currentInput}`;
7354
7354
  // Reviewer follow-up: fire fallback when no *non-sentinel*
7355
7355
  // output was produced — sentinel-only and truly empty streams
7356
7356
  // both qualify, but media-only streams (audio/image) do not.
7357
+ //
7358
+ // fallbackOnMaxSteps: false exempts one no-output shape — a turn
7359
+ // the provider reports as ended at the caller's own maxSteps bound
7360
+ // (metadata.stopReason "step-cap", a mutable reference the native
7361
+ // loops fill by drain time). That bound is the caller's budget,
7362
+ // not a provider failure, and retrying it on another provider
7363
+ // spends that provider's tokens to exceed a budget the caller set.
7364
+ const cappedByCallerBudget = enhancedOptions.fallbackOnMaxSteps === false &&
7365
+ providerStreamMetadata?.stopReason === "step-cap";
7357
7366
  if (realOutputChunks === 0 &&
7367
+ !cappedByCallerBudget &&
7358
7368
  !metadata.fallbackAttempted &&
7359
7369
  !enhancedOptions.disableInternalFallback &&
7360
7370
  streamState.toolCalls.length === 0 &&
@@ -1455,6 +1455,13 @@ export class AnthropicProvider extends BaseProvider {
1455
1455
  const client = this.client;
1456
1456
  const toolsUsed = [];
1457
1457
  const streamStartTime = Date.now();
1458
+ // Mutable-reference contract from StreamResult.metadata: created before
1459
+ // the loop and filled once the turn drains, because wrapper spreads
1460
+ // snapshot top-level result fields before the loop resolves. This is how
1461
+ // the Gemini/Vertex native paths already report their resolved outcome;
1462
+ // without it a consumer (e.g. the SDK's no-output fallback gate) cannot
1463
+ // tell a step-capped turn from a failed one.
1464
+ const turnMetadata = {};
1458
1465
  // Hoisted out of runLoop so the error path can resolve the usage
1459
1466
  // accumulated by steps that completed BEFORE the failure — those steps
1460
1467
  // were billed and must not be reported as zero.
@@ -1738,6 +1745,20 @@ export class AnthropicProvider extends BaseProvider {
1738
1745
  totalCacheRead += result.usage.cacheReadTokens ?? 0;
1739
1746
  totalCacheWrite += result.usage.cacheWriteTokens ?? 0;
1740
1747
  lastStop = result.rawStopReason ?? lastStop;
1748
+ turnMetadata.finishReason = result.finishReason;
1749
+ if (result.rawStopReason) {
1750
+ turnMetadata.rawFinishReason = result.rawStopReason;
1751
+ }
1752
+ // "tool-calls" after a drained turn means the model still wanted tools
1753
+ // when the step budget ran out — the engine breaks at maxSteps, and a
1754
+ // model turn that finished normally maps to "stop". The one other
1755
+ // producer of stop_reason "tool_use" at turn end is a final_result
1756
+ // call (structured output), which `finalResultText` identifies, so it
1757
+ // must not read as a capped turn.
1758
+ if (result.finishReason === "tool-calls" &&
1759
+ finalResultText === undefined) {
1760
+ turnMetadata.stopReason = "step-cap";
1761
+ }
1741
1762
  resolveUsage(buildDeferredUsage());
1742
1763
  resolveFinish(lastStop ?? "stop");
1743
1764
  };
@@ -1822,6 +1843,7 @@ export class AnthropicProvider extends BaseProvider {
1822
1843
  model: this.modelName,
1823
1844
  toolCalls: [],
1824
1845
  toolResults: [],
1846
+ metadata: turnMetadata,
1825
1847
  // Wire the deferred usage/finish promises into the analytics collector
1826
1848
  // (mirrors openaiChatCompletionsBase). Without this the loop computed a
1827
1849
  // fully correct aggregate that was consumed only by the OTel span —
@@ -409,6 +409,17 @@ export type StreamOptions = {
409
409
  * Used by the Claude proxy so the proxy itself can own fallback order.
410
410
  */
411
411
  disableInternalFallback?: boolean;
412
+ /**
413
+ * Whether a turn that ended at the caller's own `maxSteps` bound may still
414
+ * trigger the internal no-output provider fallback. Reaching `maxSteps` is
415
+ * a budget the caller set, not a provider failure, but such a turn can end
416
+ * with no text output and would otherwise be retried on a different
417
+ * provider — spending that provider's tokens because the caller's own
418
+ * budget ran out. Set `false` to surface the capped turn as-is
419
+ * (`metadata.stopReason === "step-cap"`). Default (unset/true) preserves
420
+ * the existing fallback behaviour.
421
+ */
422
+ fallbackOnMaxSteps?: boolean;
412
423
  /**
413
424
  * Skip injecting tool schemas into the system prompt.
414
425
  * When true, tools are ONLY passed natively via the provider's `tools` parameter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "11.24.2",
3
+ "version": "11.25.1",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {