@juspay/neurolink 12.14.1 → 12.14.2

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/dist/neurolink.js CHANGED
@@ -4159,6 +4159,22 @@ Current user's request: ${currentInput}`;
4159
4159
  middleware: options.middleware,
4160
4160
  conversationMessages: options.conversationMessages,
4161
4161
  credentials: options.credentials,
4162
+ // Extended thinking. Every provider gates it on `thinkingConfig`, so
4163
+ // omitting the field here meant it never reached one: the request went
4164
+ // out with no thinking block and the result carried no reasoning, with
4165
+ // nothing raised to say the option had been discarded. This is the same
4166
+ // failure the note above records for `disableInternalFallback` — an
4167
+ // allowlist that silently swallows a documented option.
4168
+ //
4169
+ // `thinkingConfig` is the only one of the documented thinking options
4170
+ // that `GenerateOptions` actually declares. `thinking`, `thinkingBudget`
4171
+ // and `thinkingLevel` are folded into a `thinkingConfig` only by the CLI,
4172
+ // in `src/lib/utils/thinkingConfig.ts`; nothing on the SDK path does that
4173
+ // merge. They exist solely on the internal `TextGenerationOptions`, so no
4174
+ // caller can pass them through
4175
+ // generate() today. Declaring them is a public-type decision and is
4176
+ // deliberately left out of this fix.
4177
+ thinkingConfig: options.thinkingConfig,
4162
4178
  // Lifecycle callbacks must reach the provider so non-AI-SDK paths
4163
4179
  // (Vertex's native @google/genai, native Bedrock, Ollama, etc.) can
4164
4180
  // invoke them directly. Pipeline A also still receives them via the
@@ -292,6 +292,12 @@ const messagesToAnthropic = (msgs) => {
292
292
  }
293
293
  case "assistant": {
294
294
  const blocks = [];
295
+ // Extended thinking must come back byte-identical — signature
296
+ // included — or Anthropic rejects the turn, and the loop replays this
297
+ // message on every tool step. Blocks are emitted in content order
298
+ // rather than hoisted: `interleaved-thinking-2025-05-14` (requested in
299
+ // the beta header) lets thinking appear between tool calls, so
300
+ // reordering would corrupt the chain it validates.
295
301
  for (const part of partsOf(msg.content)) {
296
302
  if (typeof part === "string") {
297
303
  if (part.length > 0) {
@@ -300,6 +306,29 @@ const messagesToAnthropic = (msgs) => {
300
306
  continue;
301
307
  }
302
308
  const p = part;
309
+ if (p?.type === "reasoning") {
310
+ const meta = p.providerOptions?.anthropic;
311
+ const redacted = meta?.redactedData;
312
+ if (typeof redacted === "string" && redacted.length > 0) {
313
+ blocks.push({ type: "redacted_thinking", data: redacted });
314
+ continue;
315
+ }
316
+ const signature = meta?.signature;
317
+ // Both halves required, matching loopAdapter's check on the
318
+ // streaming path: Anthropic rejects a thinking block that is
319
+ // unsigned, and equally one whose text is empty. Reasoning from a
320
+ // provider that never produced a signature (a reasoner model's
321
+ // plain text) is not an Anthropic thinking block at all, and an
322
+ // empty one carries nothing worth replaying — either way, dropping
323
+ // it beats sending a block that will be refused.
324
+ if (typeof signature === "string" &&
325
+ signature.length > 0 &&
326
+ typeof p.text === "string" &&
327
+ p.text.length > 0) {
328
+ blocks.push({ type: "thinking", thinking: p.text, signature });
329
+ }
330
+ continue;
331
+ }
303
332
  if (p?.type === "text" && typeof p.text === "string") {
304
333
  if (p.text.length > 0) {
305
334
  const cc = cacheControlOf(p);
@@ -1118,15 +1147,28 @@ export class AnthropicProvider extends BaseProvider {
1118
1147
  ? { topP: options.topP }
1119
1148
  : {}),
1120
1149
  }, "anthropic.doGenerate");
1150
+ // Dropping a caller's explicit sampling parameters is exactly the
1151
+ // kind of silent discard this change fixes elsewhere, so say so.
1152
+ if (thinking &&
1153
+ (samplingParams.temperature !== undefined ||
1154
+ samplingParams.topP !== undefined)) {
1155
+ logger.debug("[anthropic] extended thinking is enabled, so temperature/top_p are omitted — Anthropic rejects any temperature but 1 while thinking is set");
1156
+ }
1121
1157
  const params = {
1122
1158
  model: modelId,
1123
1159
  messages: cachedMessages,
1124
1160
  max_tokens: resolveClaudeMaxTokens(modelId, options.maxOutputTokens),
1125
1161
  ...(system ? { system } : {}),
1126
- ...(samplingParams.temperature !== undefined
1162
+ // Extended thinking fixes sampling: Anthropic rejects any
1163
+ // temperature but 1 while `thinking` is set, and does not honour
1164
+ // top_p there. The CLI always sends a default temperature, so
1165
+ // forwarding it alongside thinking turns a call that used to work
1166
+ // into a 400. Drop the sampling knobs for exactly those turns and
1167
+ // let Anthropic's thinking defaults stand.
1168
+ ...(!thinking && samplingParams.temperature !== undefined
1127
1169
  ? { temperature: samplingParams.temperature }
1128
1170
  : {}),
1129
- ...(samplingParams.topP !== undefined
1171
+ ...(!thinking && samplingParams.topP !== undefined
1130
1172
  ? { top_p: samplingParams.topP }
1131
1173
  : {}),
1132
1174
  ...(options.stopSequences && options.stopSequences.length > 0
@@ -1187,7 +1229,29 @@ export class AnthropicProvider extends BaseProvider {
1187
1229
  let jsonToolAnswered = false;
1188
1230
  for (const block of response.content) {
1189
1231
  if (block.type === "thinking") {
1190
- content.push({ type: "reasoning", text: block.thinking });
1232
+ // The signature rides along in providerOptions because Anthropic
1233
+ // rejects a replayed thinking block without it, and the tool loop
1234
+ // pushes this part straight back into the conversation.
1235
+ content.push({
1236
+ type: "reasoning",
1237
+ text: block.thinking,
1238
+ providerOptions: {
1239
+ anthropic: { signature: block.signature },
1240
+ },
1241
+ });
1242
+ }
1243
+ else if (block.type === "redacted_thinking") {
1244
+ // Encrypted reasoning: no readable text, but it must still be
1245
+ // replayed verbatim or the turn is rejected.
1246
+ content.push({
1247
+ type: "reasoning",
1248
+ text: "",
1249
+ providerOptions: {
1250
+ anthropic: {
1251
+ redactedData: block.data,
1252
+ },
1253
+ },
1254
+ });
1191
1255
  }
1192
1256
  else if (block.type === "text") {
1193
1257
  // In forced-json mode the payload arrives via the tool input, not
@@ -1824,6 +1888,9 @@ export class AnthropicProvider extends BaseProvider {
1824
1888
  const streamSamplingParams = resolveSamplingParams("anthropic", modelId, options.temperature !== undefined && options.temperature !== null
1825
1889
  ? { temperature: options.temperature }
1826
1890
  : {}, "anthropic.executeStream");
1891
+ if (thinking && streamSamplingParams.temperature !== undefined) {
1892
+ logger.debug("[anthropic] extended thinking is enabled, so temperature is omitted on the stream path — Anthropic rejects any temperature but 1 while thinking is set");
1893
+ }
1827
1894
  return {
1828
1895
  model: modelId,
1829
1896
  messages: cachedConversation,
@@ -1833,7 +1900,9 @@ export class AnthropicProvider extends BaseProvider {
1833
1900
  // the adapter immediately overwrites — and forced this whole params
1834
1901
  // object into the streaming variant for a field it does not own.
1835
1902
  ...(payload.system ? { system: payload.system } : {}),
1836
- ...(streamSamplingParams.temperature !== undefined
1903
+ // Same constraint on the streaming path: a temperature alongside
1904
+ // `thinking` is rejected outright.
1905
+ ...(!thinking && streamSamplingParams.temperature !== undefined
1837
1906
  ? { temperature: streamSamplingParams.temperature }
1838
1907
  : {}),
1839
1908
  ...(cachedTools && cachedTools.length > 0
@@ -275,6 +275,7 @@ export type LanguageModelV3Content = {
275
275
  } | {
276
276
  type: "reasoning";
277
277
  text: string;
278
+ providerOptions?: Record<string, Record<string, unknown>>;
278
279
  } | {
279
280
  type: "file";
280
281
  data: unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.14.1",
3
+ "version": "12.14.2",
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": {
@@ -110,6 +110,7 @@
110
110
  "test:new-providers": "pnpm exec tsx test/continuous-test-suite-new-providers.ts",
111
111
  "test:matrix": "pnpm exec tsx test/continuous-test-suite-provider-matrix.ts",
112
112
  "test:matrix:cli": "pnpm exec tsx test/continuous-test-suite-provider-matrix-cli.ts",
113
+ "test:thinking-config": "pnpm exec tsx test/continuous-test-suite-sdk-thinking-config.ts",
113
114
  "test:mcp:spans": "pnpm exec tsx test/continuous-test-suite-mcp-spans.ts",
114
115
  "test:mcp:infra": "pnpm exec tsx test/continuous-test-suite-mcp-infra.ts",
115
116
  "test:vendor-recovery": "pnpm exec tsx test/continuous-test-suite-native-vendor-recovery.ts",
@@ -179,10 +180,10 @@
179
180
  "docs:validate": "tsx tools/content/documentationSync.ts --validate",
180
181
  "docs:generate": "pnpm run docs:validate",
181
182
  "// Documentation (Docusaurus)": "",
182
- "docs:start": "pnpm --filter ./docs-site start",
183
- "docs:build": "pnpm --filter ./docs-site build",
184
- "docs:serve": "pnpm --filter ./docs-site serve",
185
- "docs:clear": "pnpm --filter ./docs-site clear",
183
+ "docs:start": "pnpm --dir docs-site start",
184
+ "docs:build": "pnpm --dir docs-site build",
185
+ "docs:serve": "pnpm --dir docs-site serve",
186
+ "docs:clear": "pnpm --dir docs-site clear",
186
187
  "// Proxy Observability (Local OpenObserve)": "",
187
188
  "proxy:observability:setup": "bash scripts/observability/manage-local-openobserve.sh setup",
188
189
  "proxy:observability:up": "bash scripts/observability/manage-local-openobserve.sh up",