@juspay/neurolink 12.16.0 → 12.17.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.
- package/CHANGELOG.md +3 -3
- package/dist/browser/neurolink.min.js +381 -377
- package/dist/context/nativeGenerateGuard.d.ts +6 -0
- package/dist/context/nativeGenerateGuard.js +192 -0
- package/dist/context/toolOutputLimits.js +76 -5
- package/dist/core/modules/ToolsManager.d.ts +45 -3
- package/dist/core/modules/ToolsManager.js +193 -8
- package/dist/core/nativeGenerateLoop.js +25 -5
- package/dist/providers/anthropic/client.js +15 -5
- package/dist/providers/openaiChatCompletionsBase.js +15 -0
- package/dist/types/config.d.ts +10 -0
- package/dist/types/context.d.ts +15 -0
- package/dist/types/generate.d.ts +9 -0
- package/dist/utils/tokenEstimation.d.ts +13 -0
- package/dist/utils/tokenEstimation.js +6 -1
- package/dist/utils/toolChoice.d.ts +19 -0
- package/dist/utils/toolChoice.js +39 -0
- package/package.json +4 -1
|
@@ -34,7 +34,7 @@ import { transformToolExecutions } from "../../utils/transformationUtils.js";
|
|
|
34
34
|
import { createAnthropicConfig, getProviderModel, validateApiKey, } from "../../utils/providerConfig.js";
|
|
35
35
|
import { composeAbortSignals, composeAbortSignalsScoped, createTimeoutController, mergeAbortSignals, TimeoutError, } from "../../utils/timeout.js";
|
|
36
36
|
import { raceWithAbort } from "../../utils/async/index.js";
|
|
37
|
-
import { resolveToolChoice } from "../../utils/toolChoice.js";
|
|
37
|
+
import { normalizeResolvedToolChoice, resolveToolChoice, } from "../../utils/toolChoice.js";
|
|
38
38
|
import { emitToolEndFromStepFinish } from "../../utils/toolEndEmitter.js";
|
|
39
39
|
import { NoOutputGeneratedError } from "../../utils/generationErrors.js";
|
|
40
40
|
import { buildNoOutputSentinel, stampNoOutputSpan, } from "../../utils/noOutputSentinel.js";
|
|
@@ -1074,10 +1074,20 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1074
1074
|
if (tools && tools.length === 0) {
|
|
1075
1075
|
tools = undefined;
|
|
1076
1076
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1077
|
+
// The value here has already been through `resolveToolChoice`
|
|
1078
|
+
// upstream in `executeNativeGenerate`, so this layer translates
|
|
1079
|
+
// rather than re-resolves — re-resolving would apply the auto/none
|
|
1080
|
+
// defaulting twice, and `toolsRecord`/`shouldUseTools` are not in
|
|
1081
|
+
// scope inside `getAISDKModel` in any case.
|
|
1082
|
+
//
|
|
1083
|
+
// Only the SHAPE varies, and reading `.type` off it was the bug:
|
|
1084
|
+
// a bare `"required"` has no `.type`, so the translator received
|
|
1085
|
+
// `undefined` and the field was dropped from the request entirely.
|
|
1086
|
+
// Normalisation now lives in the shared util rather than being
|
|
1087
|
+
// re-derived here, so this site cannot drift from its siblings again.
|
|
1088
|
+
const normalisedToolChoice = normalizeResolvedToolChoice(options.toolChoice);
|
|
1089
|
+
let toolChoice = normalisedToolChoice
|
|
1090
|
+
? toolChoiceToAnthropic(normalisedToolChoice)
|
|
1081
1091
|
: undefined;
|
|
1082
1092
|
// JSON/structured output: Anthropic has no response_format — emulate
|
|
1083
1093
|
// with a forced synthetic tool whose input IS the JSON payload (the
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
import { trace } from "@opentelemetry/api";
|
|
21
21
|
import { getAvailableInputTokens, getRuntimeContextWindow, getRuntimeOutputCeiling, registerRuntimeContextWindow, } from "../constants/contextWindows.js";
|
|
22
22
|
import { guardOpenAICompatConversation } from "../context/openaiCompatLoopGuard.js";
|
|
23
|
+
import { estimateTokens, serializeForEstimate, } from "../utils/tokenEstimation.js";
|
|
24
|
+
import { createNativeGenerateGuard } from "../context/nativeGenerateGuard.js";
|
|
23
25
|
import { isContextOverflowError, parseProviderOverflowDetails, } from "../context/errorDetection.js";
|
|
24
26
|
import { ContextBudgetExceededError } from "../context/errors.js";
|
|
25
27
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
@@ -899,6 +901,19 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
899
901
|
// legal again on a vendor that refuses the two together.
|
|
900
902
|
withTools = true) => runNativeGenerateLoop({
|
|
901
903
|
doGenerate,
|
|
904
|
+
...createNativeGenerateGuard({
|
|
905
|
+
provider: this.providerName,
|
|
906
|
+
availableInputTokens: getAvailableInputTokens(this.providerName, modelId, options.maxTokens ?? undefined),
|
|
907
|
+
// Runs on every step of the generate loop, so a cyclic or
|
|
908
|
+
// oversized tool schema must not throw the turn away. Shares the
|
|
909
|
+
// estimator's own serializer: its fallback over-reports, which
|
|
910
|
+
// makes the guard fire rather than silently size the overhead at
|
|
911
|
+
// nothing.
|
|
912
|
+
getFixedOverheadTokens: () => estimateTokens(serializeForEstimate({
|
|
913
|
+
tools: v3Tools,
|
|
914
|
+
responseFormat: format,
|
|
915
|
+
}), this.providerName),
|
|
916
|
+
}),
|
|
902
917
|
conversation: conv,
|
|
903
918
|
...(withTools && v3Tools ? { tools: v3Tools } : {}),
|
|
904
919
|
toolsRecord,
|
package/dist/types/config.d.ts
CHANGED
|
@@ -415,6 +415,16 @@ export type ToolConfig = {
|
|
|
415
415
|
enableMCPTools?: boolean;
|
|
416
416
|
/** Whether the bash command execution tool should be enabled (opt-in, defaults to false) */
|
|
417
417
|
enableBashTool?: boolean;
|
|
418
|
+
/**
|
|
419
|
+
* Byte ceiling for the safety-net truncation `ToolsManager` applies to
|
|
420
|
+
* every direct/custom/external-MCP tool result before it reaches the AI
|
|
421
|
+
* SDK accumulator (BZ-666). Independent of `mcp.outputLimits`, which only
|
|
422
|
+
* governs external MCP results earlier in the pipeline via its own
|
|
423
|
+
* externalize-to-artifact-store strategy. Default: 51200 (50 KB) —
|
|
424
|
+
* unchanged from the previous hard-coded ceiling, so existing consumers
|
|
425
|
+
* see no behavior change unless they set this explicitly.
|
|
426
|
+
*/
|
|
427
|
+
outputTruncationMaxBytes?: number;
|
|
418
428
|
};
|
|
419
429
|
/**
|
|
420
430
|
* Backup metadata information
|
package/dist/types/context.d.ts
CHANGED
|
@@ -373,6 +373,15 @@ export type ToolOutputPreviewOptions = {
|
|
|
373
373
|
headRatio?: number;
|
|
374
374
|
/** Fraction of preview budget allocated to the tail (default: 0.75) */
|
|
375
375
|
tailRatio?: number;
|
|
376
|
+
/**
|
|
377
|
+
* Override the omission notice spliced between head and tail. A string is
|
|
378
|
+
* used verbatim; a function receives the omitted byte count and returns
|
|
379
|
+
* the notice text. When omitted, the built-in default is used, which
|
|
380
|
+
* names the `retrieve_context` tool — pass this when that tool is not
|
|
381
|
+
* registered on the calling instance so the model isn't pointed at a tool
|
|
382
|
+
* that doesn't exist.
|
|
383
|
+
*/
|
|
384
|
+
notice?: string | ((omittedBytes: number) => string);
|
|
376
385
|
};
|
|
377
386
|
/** Result of tool output preview generation. */
|
|
378
387
|
export type ToolOutputPreviewResult = {
|
|
@@ -562,4 +571,10 @@ export type FileSummarizationServiceOptions = {
|
|
|
562
571
|
provider?: string;
|
|
563
572
|
model?: string;
|
|
564
573
|
};
|
|
574
|
+
/** Per-turn configuration for reclaiming a native V3 conversation. */
|
|
575
|
+
export type NativeGenerateGuardConfig = {
|
|
576
|
+
provider: string;
|
|
577
|
+
availableInputTokens: number;
|
|
578
|
+
getFixedOverheadTokens: () => number;
|
|
579
|
+
};
|
|
565
580
|
export {};
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -1604,6 +1604,15 @@ export type GenerateOptionsNormalized = GenerateOptions & {
|
|
|
1604
1604
|
* `doGenerate`; the provider supplies the wire details.
|
|
1605
1605
|
*/
|
|
1606
1606
|
export type NativeGenerateLoopArgs = {
|
|
1607
|
+
/** Observed usage for calibrating the next step against the last request. */
|
|
1608
|
+
observeUsage?: (usage: unknown) => void;
|
|
1609
|
+
/**
|
|
1610
|
+
* Per-step context reclaim, called before every model call with the
|
|
1611
|
+
* conversation as it now stands. Return a replacement to have the loop adopt
|
|
1612
|
+
* it, or undefined to leave it untouched. The provider owns this because the
|
|
1613
|
+
* reclaim has to understand its wire shape.
|
|
1614
|
+
*/
|
|
1615
|
+
guardConversation?: (conversation: Array<Record<string, unknown>>) => Array<Record<string, unknown>> | undefined;
|
|
1607
1616
|
doGenerate: (options: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
1608
1617
|
/** Conversation in the message-builder shape each doGenerate converts itself. */
|
|
1609
1618
|
conversation: Array<Record<string, unknown>>;
|
|
@@ -47,6 +47,19 @@ export declare function getProviderMultiplier(provider?: string): number;
|
|
|
47
47
|
* @returns Estimated token count
|
|
48
48
|
*/
|
|
49
49
|
export declare function estimateTokens(text: string, provider?: string, isCode?: boolean): number;
|
|
50
|
+
/**
|
|
51
|
+
* Serialize an arbitrary value for estimation. Never throws: `JSON.stringify`
|
|
52
|
+
* raises RangeError once a value exceeds V8's max string length, and a tool
|
|
53
|
+
* argument blob is exactly the shape that gets there. A payload that large is
|
|
54
|
+
* charged at the fallback size rather than aborting the estimate (and with it
|
|
55
|
+
* the whole turn).
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* Serialize a value for token estimation. The fallback is deliberately huge:
|
|
59
|
+
* an estimator that cannot measure a value must over-report, so a budget guard
|
|
60
|
+
* fires rather than waving through content it failed to size.
|
|
61
|
+
*/
|
|
62
|
+
export declare function serializeForEstimate(value: unknown): string;
|
|
50
63
|
/**
|
|
51
64
|
* Estimate token count for a single ChatMessage.
|
|
52
65
|
* Includes message framing overhead.
|
|
@@ -93,7 +93,12 @@ export function estimateTokens(text, provider, isCode) {
|
|
|
93
93
|
* charged at the fallback size rather than aborting the estimate (and with it
|
|
94
94
|
* the whole turn).
|
|
95
95
|
*/
|
|
96
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Serialize a value for token estimation. The fallback is deliberately huge:
|
|
98
|
+
* an estimator that cannot measure a value must over-report, so a budget guard
|
|
99
|
+
* fires rather than waving through content it failed to size.
|
|
100
|
+
*/
|
|
101
|
+
export function serializeForEstimate(value) {
|
|
97
102
|
if (typeof value === "string") {
|
|
98
103
|
return value;
|
|
99
104
|
}
|
|
@@ -2,3 +2,22 @@ import type { Tool, ToolChoice } from "../types/index.js";
|
|
|
2
2
|
export declare function resolveToolChoice(options: {
|
|
3
3
|
toolChoice?: ToolChoice<Record<string, Tool>>;
|
|
4
4
|
}, tools: Record<string, Tool> | undefined, shouldUseTools: boolean): ToolChoice<Record<string, Tool>> | "none";
|
|
5
|
+
/**
|
|
6
|
+
* Normalise an already-resolved tool choice into the shape
|
|
7
|
+
* `toolChoiceToAnthropic` (and its peers) accept.
|
|
8
|
+
*
|
|
9
|
+
* A provider's model handle sees a value that has *already* been through
|
|
10
|
+
* `resolveToolChoice` upstream, so it must translate rather than re-resolve —
|
|
11
|
+
* re-resolving there would apply the `auto`/`none` defaulting twice, and the
|
|
12
|
+
* `tools`/`shouldUseTools` inputs it needs are not in scope at that layer
|
|
13
|
+
* anyway.
|
|
14
|
+
*
|
|
15
|
+
* What does vary is the shape. The value arrives either as a bare string
|
|
16
|
+
* (`"auto"` / `"none"` / `"required"`), as `{ type: "tool", toolName }`, or —
|
|
17
|
+
* on the native loop's tool-free re-ask — as an injected `{ type: "none" }`
|
|
18
|
+
* object. Reading `.type` off the string forms yields `undefined`, which is
|
|
19
|
+
* how string choices were silently dropped from the Anthropic generate
|
|
20
|
+
* request. This exists so that normalisation lives in one place instead of
|
|
21
|
+
* being re-derived at each call site.
|
|
22
|
+
*/
|
|
23
|
+
export declare function normalizeResolvedToolChoice(value: unknown): ToolChoice<Record<string, Tool>> | undefined;
|
package/dist/utils/toolChoice.js
CHANGED
|
@@ -4,3 +4,42 @@ export function resolveToolChoice(options, tools, shouldUseTools) {
|
|
|
4
4
|
}
|
|
5
5
|
return options.toolChoice ?? "auto";
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Normalise an already-resolved tool choice into the shape
|
|
9
|
+
* `toolChoiceToAnthropic` (and its peers) accept.
|
|
10
|
+
*
|
|
11
|
+
* A provider's model handle sees a value that has *already* been through
|
|
12
|
+
* `resolveToolChoice` upstream, so it must translate rather than re-resolve —
|
|
13
|
+
* re-resolving there would apply the `auto`/`none` defaulting twice, and the
|
|
14
|
+
* `tools`/`shouldUseTools` inputs it needs are not in scope at that layer
|
|
15
|
+
* anyway.
|
|
16
|
+
*
|
|
17
|
+
* What does vary is the shape. The value arrives either as a bare string
|
|
18
|
+
* (`"auto"` / `"none"` / `"required"`), as `{ type: "tool", toolName }`, or —
|
|
19
|
+
* on the native loop's tool-free re-ask — as an injected `{ type: "none" }`
|
|
20
|
+
* object. Reading `.type` off the string forms yields `undefined`, which is
|
|
21
|
+
* how string choices were silently dropped from the Anthropic generate
|
|
22
|
+
* request. This exists so that normalisation lives in one place instead of
|
|
23
|
+
* being re-derived at each call site.
|
|
24
|
+
*/
|
|
25
|
+
export function normalizeResolvedToolChoice(value) {
|
|
26
|
+
if (typeof value === "string") {
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
if (value && typeof value === "object") {
|
|
30
|
+
const candidate = value;
|
|
31
|
+
if (candidate.type === "tool") {
|
|
32
|
+
// A tool choice that names no tool cannot be honoured. Fall through to
|
|
33
|
+
// the provider default rather than emitting the bare string "tool",
|
|
34
|
+
// which is not one of the valid auto/none/required forms and would be
|
|
35
|
+
// rejected downstream.
|
|
36
|
+
return candidate.toolName
|
|
37
|
+
? { type: "tool", toolName: candidate.toolName }
|
|
38
|
+
: undefined;
|
|
39
|
+
}
|
|
40
|
+
if (candidate.type) {
|
|
41
|
+
return candidate.type;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.17.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": {
|
|
@@ -117,6 +117,7 @@
|
|
|
117
117
|
"test:thinking-config": "pnpm exec tsx test/continuous-test-suite-sdk-thinking-config.ts",
|
|
118
118
|
"test:gemini-thinking-level": "pnpm exec tsx test/continuous-test-suite-gemini-thinking-level.ts",
|
|
119
119
|
"test:gemini-thinking-level-ai-studio": "pnpm exec tsx test/continuous-test-suite-gemini-thinking-level-ai-studio.ts",
|
|
120
|
+
"test:anthropic-toolchoice": "pnpm exec tsx test/continuous-test-suite-anthropic-toolchoice.ts",
|
|
120
121
|
"test:mcp:spans": "pnpm exec tsx test/continuous-test-suite-mcp-spans.ts",
|
|
121
122
|
"test:mcp:infra": "pnpm exec tsx test/continuous-test-suite-mcp-infra.ts",
|
|
122
123
|
"test:vendor-recovery": "pnpm exec tsx test/continuous-test-suite-native-vendor-recovery.ts",
|
|
@@ -166,7 +167,9 @@
|
|
|
166
167
|
"test:agents-live": "tsx test/continuous-test-agents-live.ts",
|
|
167
168
|
"test:tool-routing-cli": "pnpm exec tsx test/continuous-test-suite-tool-routing-cli.ts",
|
|
168
169
|
"test:tool-dedup": "pnpm exec tsx test/continuous-test-suite-tool-dedup.ts",
|
|
170
|
+
"test:tools-manager-truncation": "pnpm exec tsx test/continuous-test-suite-tools-manager-truncation.ts",
|
|
169
171
|
"test:model-not-found-retryable": "pnpm exec tsx test/continuous-test-suite-model-not-found-retryable.ts",
|
|
172
|
+
"test:reasoning-parity-live": "pnpm exec tsx test/continuous-test-suite-reasoning-parity-live.ts",
|
|
170
173
|
"test:ci": "pnpm run test && pnpm run test:client && pnpm run test:hitl",
|
|
171
174
|
"// CI tier — fast, no live AI calls, safe for every commit (test:unit; also see the separate provider-safety-net CI job, which runs build + test:providers-mocked + test:provider-structure + test:error-classifier-contract on every PR)": "",
|
|
172
175
|
"test:tool-routing": "pnpm exec tsx test/continuous-test-suite-tool-routing.ts",
|