@juspay/neurolink 11.15.2 → 11.15.4

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.
@@ -107,8 +107,11 @@ export function runAgenticLoop(adapter, initialConversation, options) {
107
107
  catch (err) {
108
108
  throw hasEmitted ? new PostEmissionStepError(err) : err;
109
109
  }
110
- }, undefined, // no OTel span threaded through the engine today; adapters instrument their own steps if they need span-level detail
111
- `${adapter.providerLabel}.step`);
110
+ },
111
+ // The caller's span, when it passes one. withProviderRetry writes
112
+ // gen_ai.provider.total_attempts here, so a loop that threaded a
113
+ // span before it moved onto this engine keeps emitting it.
114
+ options.span, `${adapter.providerLabel}.step`);
112
115
  }
113
116
  catch (err) {
114
117
  throw err instanceof PostEmissionStepError ? err.cause : err;
@@ -1697,9 +1697,17 @@ export class AnthropicProvider extends BaseProvider {
1697
1697
  },
1698
1698
  };
1699
1699
  }
1700
+ // The active span goes with it. Before this loop moved onto the shared
1701
+ // engine it called
1702
+ // withProviderRetry(fn, trace.getActiveSpan() ?? undefined, label)
1703
+ // and that span is where gen_ai.provider.total_attempts is recorded.
1704
+ // The engine passed `undefined` in its place, so the attribute silently
1705
+ // stopped being emitted for every native Anthropic turn.
1706
+ const activeSpan = trace.getActiveSpan();
1700
1707
  const { stream, resultPromise } = runAgenticLoop(adapter, payload.messages.slice(), {
1701
1708
  tools: engineTools,
1702
1709
  ...(abortSignal ? { abortSignal } : {}),
1710
+ ...(activeSpan ? { span: activeSpan } : {}),
1703
1711
  });
1704
1712
  // Structured turns buffer their text rather than streaming it: a caller
1705
1713
  // that passed a schema needs parseable JSON, and deltas emitted before
@@ -6056,6 +6056,15 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
6056
6056
  handler: async (ctx) => {
6057
6057
  const routing = runtimeConfigProvider?.();
6058
6058
  const effectiveAllowlist = routing?.accountAllowlist ?? accountAllowlist;
6059
+ // Hot-reloaded routing wins over the value baked in at construction,
6060
+ // the same precedence the request path uses.
6061
+ const effectivePrimaryKey = routing?.primaryAccountKey ?? primaryAccountKey;
6062
+ // Rows carry a bare label in one loop and a full pool key in the
6063
+ // other; anthropicAccountKeysEqual normalises both, so neither can
6064
+ // miss the match on prefix alone.
6065
+ const isPrimaryAccount = (keyOrLabel) => !!effectivePrimaryKey &&
6066
+ !!keyOrLabel &&
6067
+ anthropicAccountKeysEqual(keyOrLabel, effectivePrimaryKey);
6059
6068
  // Cached by default. This endpoint is built to be polled, and a live
6060
6069
  // refresh spends the user's own OAuth credentials against Anthropic's
6061
6070
  // usage API — one dashboard on a short interval would hammer it.
@@ -6160,7 +6169,7 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
6160
6169
  cooling,
6161
6170
  allowed: null,
6162
6171
  expired: null,
6163
- isPrimary: false,
6172
+ isPrimary: isPrimaryAccount(result.key ?? label),
6164
6173
  requests: stat ? stat.successCount + stat.errorCount : null,
6165
6174
  errors: stat?.errorCount ?? null,
6166
6175
  rateLimits: stat?.rateLimitCount ?? null,
@@ -6204,7 +6213,7 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
6204
6213
  cooling: false,
6205
6214
  allowed: null,
6206
6215
  expired: null,
6207
- isPrimary: false,
6216
+ isPrimary: isPrimaryAccount(entry.label),
6208
6217
  requests: entry.successCount + entry.errorCount,
6209
6218
  errors: entry.errorCount,
6210
6219
  rateLimits: entry.rateLimitCount,
@@ -1,4 +1,5 @@
1
1
  import type Anthropic from "@anthropic-ai/sdk";
2
+ import type { Span } from "@opentelemetry/api";
2
3
  import type { Tool } from "./tools.js";
3
4
  import type { CollectedChunkResult, NativeFunctionCall, NativeToolDeclarationsResult } from "./providers.js";
4
5
  /**
@@ -292,6 +293,19 @@ export type AgenticLoopOptions = {
292
293
  execute?: (args: Record<string, unknown>, opts: unknown) => Promise<unknown>;
293
294
  }>;
294
295
  abortSignal?: AbortSignal;
296
+ /**
297
+ * Span the per-step provider retry annotates, via
298
+ * `withProviderRetry(..., span, ...)` — it records
299
+ * `gen_ai.provider.total_attempts` on every completed step, retried or not.
300
+ *
301
+ * Caller-supplied rather than read from the ambient context inside the
302
+ * engine. Reading it here would hand the attribute to every provider on the
303
+ * engine, including ones whose hand-rolled loops never emitted it, and a
304
+ * refactor that silently ADDS observable behaviour is the same defect as one
305
+ * that silently drops it. Today only the direct Anthropic loops set this,
306
+ * because only they threaded a span before moving onto the engine.
307
+ */
308
+ span?: Span;
295
309
  };
296
310
  export type AgenticLoopResult<TConversation> = {
297
311
  text: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "11.15.2",
3
+ "version": "11.15.4",
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": {