@juspay/neurolink 12.17.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.
@@ -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
- let toolChoice = options.toolChoice
1078
- ? toolChoiceToAnthropic(options.toolChoice.type === "tool"
1079
- ? { type: "tool", toolName: options.toolChoice.toolName }
1080
- : options.toolChoice.type)
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
@@ -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;
@@ -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.17.0",
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",