@juspay/neurolink 11.24.1 → 11.25.0
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/CHANGELOG.md +3 -3
- package/dist/browser/neurolink.min.js +207 -207
- package/dist/core/baseProvider.js +8 -2
- package/dist/neurolink.js +10 -0
- package/dist/providers/anthropic/client.js +22 -0
- package/dist/providers/googleVertex/client.js +54 -47
- package/dist/types/stream.d.ts +11 -0
- package/package.json +1 -1
|
@@ -247,8 +247,14 @@ export class BaseProvider {
|
|
|
247
247
|
return this.wrapStreamWithLifecycleCallbacks(realStreamResult, options);
|
|
248
248
|
}
|
|
249
249
|
catch (realStreamError) {
|
|
250
|
-
//
|
|
251
|
-
//
|
|
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 —
|
|
@@ -3337,6 +3337,55 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3337
3337
|
const contextGuard = createContextGuard(getContextWindowSize("vertex", modelName));
|
|
3338
3338
|
const failedTools = new Map();
|
|
3339
3339
|
try {
|
|
3340
|
+
// Restores the per-tool observation the hand-rolled dispatch had.
|
|
3341
|
+
// The execution runs INSIDE the span's context so spans the tool opens
|
|
3342
|
+
// itself nest under this call rather than dangling beside the turn, and
|
|
3343
|
+
// the settled RESULT is inspected — an MCP tool reports failure in its
|
|
3344
|
+
// payload, so an observation that only watches for throws records a
|
|
3345
|
+
// failed call as successful.
|
|
3346
|
+
//
|
|
3347
|
+
// Stream path only: the generate twin never had per-tool spans, and
|
|
3348
|
+
// giving it them here would be behaviour GAINED under cover of a
|
|
3349
|
+
// migration. Shared with resolveToolOnMiss below: the old dispatch
|
|
3350
|
+
// opened the span before the executor lookup, so a tool hydrated
|
|
3351
|
+
// mid-turn was observed exactly like one declared up front.
|
|
3352
|
+
const withToolSpan = (toolCallName, run) => {
|
|
3353
|
+
const toolSpan = tracers.mcp.startSpan("ai.toolCall", {
|
|
3354
|
+
kind: SpanKind.INTERNAL,
|
|
3355
|
+
attributes: {
|
|
3356
|
+
[LANGFUSE_ATTR.OBSERVATION_TYPE]: "tool",
|
|
3357
|
+
[ATTR.GEN_AI_TOOL_NAME]: toolCallName,
|
|
3358
|
+
"ai.toolCall.name": toolCallName,
|
|
3359
|
+
},
|
|
3360
|
+
}, turnContext);
|
|
3361
|
+
const finish = (output, errorMessage) => {
|
|
3362
|
+
toolSpan.setAttribute("ai.toolCall.result", spanJsonAttribute(output));
|
|
3363
|
+
toolSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_OUTPUT, spanJsonAttribute(output));
|
|
3364
|
+
if (errorMessage) {
|
|
3365
|
+
toolSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_LEVEL, "ERROR");
|
|
3366
|
+
toolSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_STATUS_MESSAGE, errorMessage);
|
|
3367
|
+
toolSpan.setStatus({
|
|
3368
|
+
code: SpanStatusCode.ERROR,
|
|
3369
|
+
message: errorMessage,
|
|
3370
|
+
});
|
|
3371
|
+
}
|
|
3372
|
+
else {
|
|
3373
|
+
toolSpan.setStatus({ code: SpanStatusCode.OK });
|
|
3374
|
+
}
|
|
3375
|
+
toolSpan.end();
|
|
3376
|
+
};
|
|
3377
|
+
return otelContext.with(otelTrace.setSpan(turnContext, toolSpan), async () => {
|
|
3378
|
+
try {
|
|
3379
|
+
const result = await run();
|
|
3380
|
+
finish(result, extractMcpToolErrorMessage(result));
|
|
3381
|
+
return result;
|
|
3382
|
+
}
|
|
3383
|
+
catch (error) {
|
|
3384
|
+
finish({ error: true }, error instanceof Error ? error.message : String(error));
|
|
3385
|
+
throw error;
|
|
3386
|
+
}
|
|
3387
|
+
});
|
|
3388
|
+
};
|
|
3340
3389
|
// Executors handed to the engine, taken through the turn's
|
|
3341
3390
|
// DedupExecuteMap so an identical repeated call is answered from the
|
|
3342
3391
|
// per-turn cache rather than run again (BZ-3327). `.get()` returns the
|
|
@@ -3358,53 +3407,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3358
3407
|
toolTimeoutMs: toolExecTimeoutMs,
|
|
3359
3408
|
abortSignal: internalAbort.signal,
|
|
3360
3409
|
onProgress: () => turnClock.noteProgress(),
|
|
3361
|
-
|
|
3362
|
-
// The execution runs INSIDE the span's context so spans the tool opens
|
|
3363
|
-
// itself nest under this call rather than dangling beside the turn, and
|
|
3364
|
-
// the settled RESULT is inspected — an MCP tool reports failure in its
|
|
3365
|
-
// payload, so an observation that only watches for throws records a
|
|
3366
|
-
// failed call as successful.
|
|
3367
|
-
//
|
|
3368
|
-
// Stream path only: the generate twin never had per-tool spans, and
|
|
3369
|
-
// giving it them here would be behaviour GAINED under cover of a
|
|
3370
|
-
// migration.
|
|
3371
|
-
withToolSpan: (toolCallName, run) => {
|
|
3372
|
-
const toolSpan = tracers.mcp.startSpan("ai.toolCall", {
|
|
3373
|
-
kind: SpanKind.INTERNAL,
|
|
3374
|
-
attributes: {
|
|
3375
|
-
[LANGFUSE_ATTR.OBSERVATION_TYPE]: "tool",
|
|
3376
|
-
[ATTR.GEN_AI_TOOL_NAME]: toolCallName,
|
|
3377
|
-
"ai.toolCall.name": toolCallName,
|
|
3378
|
-
},
|
|
3379
|
-
}, turnContext);
|
|
3380
|
-
const finish = (output, errorMessage) => {
|
|
3381
|
-
toolSpan.setAttribute("ai.toolCall.result", spanJsonAttribute(output));
|
|
3382
|
-
toolSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_OUTPUT, spanJsonAttribute(output));
|
|
3383
|
-
if (errorMessage) {
|
|
3384
|
-
toolSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_LEVEL, "ERROR");
|
|
3385
|
-
toolSpan.setAttribute(LANGFUSE_ATTR.OBSERVATION_STATUS_MESSAGE, errorMessage);
|
|
3386
|
-
toolSpan.setStatus({
|
|
3387
|
-
code: SpanStatusCode.ERROR,
|
|
3388
|
-
message: errorMessage,
|
|
3389
|
-
});
|
|
3390
|
-
}
|
|
3391
|
-
else {
|
|
3392
|
-
toolSpan.setStatus({ code: SpanStatusCode.OK });
|
|
3393
|
-
}
|
|
3394
|
-
toolSpan.end();
|
|
3395
|
-
};
|
|
3396
|
-
return otelContext.with(otelTrace.setSpan(turnContext, toolSpan), async () => {
|
|
3397
|
-
try {
|
|
3398
|
-
const result = await run();
|
|
3399
|
-
finish(result, extractMcpToolErrorMessage(result));
|
|
3400
|
-
return result;
|
|
3401
|
-
}
|
|
3402
|
-
catch (error) {
|
|
3403
|
-
finish({ error: true }, error instanceof Error ? error.message : String(error));
|
|
3404
|
-
throw error;
|
|
3405
|
-
}
|
|
3406
|
-
});
|
|
3407
|
-
},
|
|
3410
|
+
withToolSpan,
|
|
3408
3411
|
}),
|
|
3409
3412
|
};
|
|
3410
3413
|
}
|
|
@@ -3538,6 +3541,10 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3538
3541
|
toolTimeoutMs: toolExecTimeoutMs,
|
|
3539
3542
|
abortSignal: internalAbort.signal,
|
|
3540
3543
|
onProgress: () => turnClock.noteProgress(),
|
|
3544
|
+
// Same observation as an up-front executor: the hand-rolled
|
|
3545
|
+
// dispatch spanned at the call, after the executor lookup, so
|
|
3546
|
+
// a hydrated tool was never the one unobserved call in a turn.
|
|
3547
|
+
withToolSpan,
|
|
3541
3548
|
}),
|
|
3542
3549
|
};
|
|
3543
3550
|
},
|
package/dist/types/stream.d.ts
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "11.25.0",
|
|
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": {
|