@juspay/neurolink 12.12.13 → 12.12.15

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.
@@ -1706,9 +1706,12 @@ export class BaseProvider {
1706
1706
  analytics: result.analytics,
1707
1707
  evaluation: result.evaluation,
1708
1708
  audio: result.audio,
1709
- // Forward reasoning fields populated by the native generate loop from
1710
- // vendor reasoning parts (DeepSeek `reasoning_content`, Anthropic
1711
- // thinking, Gemini thought parts, OpenAI o1).
1709
+ // Forward reasoning fields. They are populated by the shared native
1710
+ // generate loop, which serves the OpenAI-compatible, Anthropic and
1711
+ // SageMaker providers (DeepSeek `reasoning_content`, gateway
1712
+ // `reasoning`, Anthropic thinking, OpenAI o-series). Vertex and AI Studio
1713
+ // override generate() and never reach that loop; they report a numeric
1714
+ // `usage.reasoning` token count and leave this text field undefined.
1712
1715
  reasoning: result.reasoning,
1713
1716
  reasoningTokens: result.reasoningTokens,
1714
1717
  };
@@ -16,8 +16,9 @@ export function toNativeToolDeclarations(tools, format) {
16
16
  const input_schema = (rawSchema
17
17
  ? convertZodToJsonSchema(rawSchema)
18
18
  : { type: "object", properties: {} });
19
- // Honor a cache breakpoint the caller set on this tool. The direct
20
- // Anthropic path marks the last tool itself, on the assembled request.
19
+ // Honor a cache breakpoint the caller set on this tool. Closing the
20
+ // stable prefix is a separate step: both Anthropic paths call
21
+ // `withLastToolCacheBreakpoint` on the assembled array afterwards.
21
22
  const cc = cacheControlOf(tool);
22
23
  const declaration = {
23
24
  name,
package/dist/neurolink.js CHANGED
@@ -4386,9 +4386,11 @@ Current user's request: ${currentInput}`;
4386
4386
  music: textResult.music,
4387
4387
  ppt: textResult.ppt,
4388
4388
  // Forward reasoning/reasoningTokens from the provider layer.
4389
- // The native generate loop extracts these from vendor reasoning
4389
+ // The shared native generate loop extracts these from vendor reasoning
4390
4390
  // parts (DeepSeek's `reasoning_content`, Anthropic thinking blocks,
4391
- // Gemini thought parts, OpenAI o1) and they're declared on
4391
+ // OpenAI o-series) for the providers that use it — OpenAI-compatible,
4392
+ // Anthropic and SageMaker. Vertex and AI Studio override generate() and
4393
+ // leave the text field undefined. They're declared on
4392
4394
  // `GenerateResult`, but the builder previously dropped them on the
4393
4395
  // floor — so callers asking for `result.reasoning` got `undefined`
4394
4396
  // even when the model emitted a chain-of-thought.
@@ -139,7 +139,67 @@ export class AmazonSageMakerProvider extends BaseProvider {
139
139
  if (!hasNativeDoGenerate(model)) {
140
140
  throw this.handleProviderError(new Error("sagemaker: model handle exposes no doGenerate()"));
141
141
  }
142
- const doGenerate = model.doGenerate.bind(model);
142
+ const doGenerate = async (call) => {
143
+ const format = call.responseFormat;
144
+ const result = await model.doGenerate({
145
+ ...call,
146
+ ...(call.maxOutputTokens !== undefined
147
+ ? { maxTokens: call.maxOutputTokens }
148
+ : {}),
149
+ ...(format?.type === "json"
150
+ ? {
151
+ responseFormat: format.schema
152
+ ? {
153
+ type: "json_schema",
154
+ json_schema: { name: "response", schema: format.schema },
155
+ }
156
+ : { type: "json_object" },
157
+ }
158
+ : {}),
159
+ });
160
+ // SageMaker's low-level model retains its legacy result for streaming
161
+ // and direct consumers. The shared loop consumes V3 content and usage.
162
+ if (Array.isArray(result.content)) {
163
+ return result;
164
+ }
165
+ const content = [];
166
+ if (typeof result.text === "string" && result.text) {
167
+ content.push({ type: "text", text: result.text });
168
+ }
169
+ if (typeof result.reasoning === "string" && result.reasoning) {
170
+ content.push({ type: "reasoning", text: result.reasoning });
171
+ }
172
+ for (const call of Array.isArray(result.toolCalls)
173
+ ? result.toolCalls
174
+ : []) {
175
+ if (typeof call !== "object" || call === null) {
176
+ continue;
177
+ }
178
+ const item = call;
179
+ const fn = item.function;
180
+ if (typeof item.id === "string" && typeof fn?.name === "string") {
181
+ content.push({
182
+ type: "tool-call",
183
+ toolCallId: item.id,
184
+ toolName: fn.name,
185
+ input: fn.arguments ?? "{}",
186
+ });
187
+ }
188
+ }
189
+ const usage = result.usage;
190
+ return {
191
+ ...result,
192
+ content,
193
+ usage: {
194
+ inputTokens: {
195
+ total: typeof usage?.inputTokens === "number" ? usage.inputTokens : 0,
196
+ },
197
+ outputTokens: {
198
+ total: typeof usage?.outputTokens === "number" ? usage.outputTokens : 0,
199
+ },
200
+ },
201
+ };
202
+ };
143
203
  const shouldUseTools = !options.disableTools && this.supportsTools();
144
204
  const toolsRecord = shouldUseTools
145
205
  ? options.tools || {}
@@ -1,9 +1,26 @@
1
1
  import type Anthropic from "@anthropic-ai/sdk";
2
2
  /**
3
- * Read an Anthropic cache breakpoint from a message/part/tool carrier.
4
- * MessageBuilder marks system messages with
5
- * `providerOptions.anthropic.cacheControl`; the Anthropic client marks the last
6
- * tool definition on the assembled request. This reader is what honors both.
3
+ * Default the tool cache boundary to the last tool, unless the caller chose one.
4
+ *
5
+ * Tool definitions sit between the system prompt and the conversation and
6
+ * rarely change, so with no breakpoint closing that prefix the whole tools
7
+ * block is re-billed as fresh input every turn.
8
+ *
9
+ * Call it after every tool mutation — including any appended `final_result`
10
+ * tool — and before counting markers for the remaining history budget.
11
+ * Explicit caller markers are preserved, including a non-last boundary.
12
+ * Both generate and stream apply the same default.
13
+ *
14
+ * Pure — returns a new array. A tool that already carries a breakpoint wins.
15
+ */
16
+ export declare const withLastToolCacheBreakpoint: (tools: Anthropic.Messages.Tool[] | undefined) => Anthropic.Messages.Tool[] | undefined;
17
+ /**
18
+ * Read an Anthropic cache breakpoint from a message/part/tool carrier that
19
+ * still carries `providerOptions.anthropic.cacheControl` — i.e. BEFORE
20
+ * conversion to the Anthropic wire shape. MessageBuilder marks system messages
21
+ * that way. Tools are marked after conversion, by
22
+ * `withLastToolCacheBreakpoint`, which reads the wire-shaped `cache_control`
23
+ * instead; do not use this reader on an assembled tool.
7
24
  *
8
25
  * Extracted from anthropic/client.ts so `src/lib/core/nativeToolFormat.ts`
9
26
  * can share it without importing the provider client (which would create a
@@ -1,8 +1,41 @@
1
1
  /**
2
- * Read an Anthropic cache breakpoint from a message/part/tool carrier.
3
- * MessageBuilder marks system messages with
4
- * `providerOptions.anthropic.cacheControl`; the Anthropic client marks the last
5
- * tool definition on the assembled request. This reader is what honors both.
2
+ * Default the tool cache boundary to the last tool, unless the caller chose one.
3
+ *
4
+ * Tool definitions sit between the system prompt and the conversation and
5
+ * rarely change, so with no breakpoint closing that prefix the whole tools
6
+ * block is re-billed as fresh input every turn.
7
+ *
8
+ * Call it after every tool mutation — including any appended `final_result`
9
+ * tool — and before counting markers for the remaining history budget.
10
+ * Explicit caller markers are preserved, including a non-last boundary.
11
+ * Both generate and stream apply the same default.
12
+ *
13
+ * Pure — returns a new array. A tool that already carries a breakpoint wins.
14
+ */
15
+ export const withLastToolCacheBreakpoint = (tools) => {
16
+ if (!tools || tools.length === 0) {
17
+ return tools;
18
+ }
19
+ // Read the ASSEMBLED wire field. An earlier version asked `cacheControlOf`,
20
+ // which looks at `providerOptions.anthropic.cacheControl` — a shape these
21
+ // tools no longer have by this point — so the check never fired and a
22
+ // caller-marked tool got a second, redundant marker.
23
+ if (tools.some((t) => t.cache_control !== undefined)) {
24
+ return [...tools];
25
+ }
26
+ const last = tools[tools.length - 1];
27
+ return [
28
+ ...tools.slice(0, -1),
29
+ { ...last, cache_control: { type: "ephemeral" } },
30
+ ];
31
+ };
32
+ /**
33
+ * Read an Anthropic cache breakpoint from a message/part/tool carrier that
34
+ * still carries `providerOptions.anthropic.cacheControl` — i.e. BEFORE
35
+ * conversion to the Anthropic wire shape. MessageBuilder marks system messages
36
+ * that way. Tools are marked after conversion, by
37
+ * `withLastToolCacheBreakpoint`, which reads the wire-shaped `cache_control`
38
+ * instead; do not use this reader on an assembled tool.
6
39
  *
7
40
  * Extracted from anthropic/client.ts so `src/lib/core/nativeToolFormat.ts`
8
41
  * can share it without importing the provider client (which would create a
@@ -44,7 +44,7 @@ import { createDeferredAnalytics, stringifyToolInput, } from "../openaiChatCompl
44
44
  import { createStreamChannel } from "../../core/streamChannel.js";
45
45
  import { toNativeToolDeclarations } from "../../core/nativeToolFormat.js";
46
46
  import { ANTHROPIC_BETA_HEADERS } from "./constants.js";
47
- import { cacheControlOf } from "./cacheControl.js";
47
+ import { cacheControlOf, withLastToolCacheBreakpoint } from "./cacheControl.js";
48
48
  import { appendFinalResultInstruction, appendFinalResultTool, FINAL_RESULT_TOOL_NAME, } from "./structuredOutput.js";
49
49
  // AnthropicProviderConfig is imported from types/providers.ts
50
50
  // Re-export for backward compatibility
@@ -1087,23 +1087,9 @@ export class AnthropicProvider extends BaseProvider {
1087
1087
  }
1088
1088
  // Extended thinking passthrough (providerOptions.anthropic.thinking).
1089
1089
  const thinking = options.providerOptions?.anthropic?.thinking;
1090
- // Close the stable prefix with a breakpoint on the LAST tool. Tool
1091
- // definitions sit between the system prompt and the conversation and
1092
- // rarely change, so without this the whole tools block is re-billed
1093
- // every turn. Applied after every tool mutation above (including the
1094
- // appended final_result tool) so the marker really is last, and before
1095
- // the count below so the history budget accounts for it. A caller that
1096
- // marked a tool itself wins.
1097
- if (tools && tools.length > 0) {
1098
- const alreadyMarked = tools.some((t) => cacheControlOf(t));
1099
- if (!alreadyMarked) {
1100
- const last = tools[tools.length - 1];
1101
- tools = [
1102
- ...tools.slice(0, -1),
1103
- { ...last, cache_control: { type: "ephemeral" } },
1104
- ];
1105
- }
1106
- }
1090
+ // Close the stable prefix with a breakpoint on the last tool. The
1091
+ // stream path does the same, just before its own marker count.
1092
+ tools = withLastToolCacheBreakpoint(tools);
1107
1093
  // Prompt-cache parity with the native Vertex+Claude path: upstream
1108
1094
  // layers mark the stable prefix (system via MessageBuilder, and the
1109
1095
  // last tool just above) — the growing conversation
@@ -1779,12 +1765,17 @@ export class AnthropicProvider extends BaseProvider {
1779
1765
  }
1780
1766
  }
1781
1767
  }
1768
+ // Close the stable prefix with a breakpoint on the last tool, exactly
1769
+ // as the generate path does. This was missing: the marker was applied
1770
+ // only in doGenerate, so a streaming turn re-billed the entire tools
1771
+ // block every step and could not reuse the prefix generate() cached.
1772
+ const cachedTools = withLastToolCacheBreakpoint(anthropicTools);
1782
1773
  // Prompt-cache parity with the native Vertex+Claude path — rolling
1783
1774
  // history breakpoints, re-applied per step so the stable prefix stays
1784
1775
  // byte-identical while the breakpoint follows the growing tail.
1785
1776
  const cacheMarkersUsed = countAnthropicCacheMarkers({
1786
1777
  system: payload.system,
1787
- tools: anthropicTools,
1778
+ tools: cachedTools,
1788
1779
  messages: conversation,
1789
1780
  });
1790
1781
  const cachedConversation = applyAnthropicHistoryCacheBreakpoints(conversation, ANTHROPIC_MAX_CACHE_BREAKPOINTS - cacheMarkersUsed);
@@ -1803,8 +1794,8 @@ export class AnthropicProvider extends BaseProvider {
1803
1794
  ...(streamSamplingParams.temperature !== undefined
1804
1795
  ? { temperature: streamSamplingParams.temperature }
1805
1796
  : {}),
1806
- ...(anthropicTools && anthropicTools.length > 0
1807
- ? { tools: anthropicTools }
1797
+ ...(cachedTools && cachedTools.length > 0
1798
+ ? { tools: cachedTools }
1808
1799
  : {}),
1809
1800
  ...(anthropicToolChoice ? { tool_choice: anthropicToolChoice } : {}),
1810
1801
  ...(thinking ? { thinking } : {}),
@@ -399,8 +399,8 @@ export class SageMakerLanguageModel {
399
399
  ...(options.maxTokens !== undefined
400
400
  ? { max_new_tokens: options.maxTokens }
401
401
  : {}),
402
- temperature: options.temperature || 0.7,
403
- top_p: options.topP || 0.9,
402
+ temperature: options.temperature ?? 0.7,
403
+ top_p: options.topP ?? 0.9,
404
404
  stop: options.stopSequences || [],
405
405
  },
406
406
  };
@@ -582,9 +582,14 @@ export class SageMakerLanguageModel {
582
582
  // Handle response with tool calls
583
583
  if (responseBody.choices && Array.isArray(responseBody.choices)) {
584
584
  const choice = responseBody.choices[0];
585
- if (choice?.message?.content) {
585
+ if (typeof choice?.message?.content === "string") {
586
586
  return choice.message.content;
587
587
  }
588
+ // A tool-only assistant turn has no text. Serializing the entire
589
+ // endpoint response here would replay its envelope as assistant prose.
590
+ if (choice?.message?.tool_calls?.length) {
591
+ return "";
592
+ }
588
593
  }
589
594
  // Fallback: stringify the entire response
590
595
  return JSON.stringify(responseBody);
@@ -3,18 +3,6 @@
3
3
  * Service registry, dependency injection, and service management types
4
4
  */
5
5
  import type { UnknownRecord } from "./common.js";
6
- /**
7
- * Service factory function type
8
- */
9
- export type ServiceFactory<T = unknown> = () => T | Promise<T>;
10
- /**
11
- * Service registration configuration
12
- */
13
- export type ServiceRegistration<T = unknown> = {
14
- factory: ServiceFactory<T>;
15
- singleton: boolean;
16
- instance?: T;
17
- };
18
6
  /**
19
7
  * Service definition with metadata and status
20
8
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.12.13",
3
+ "version": "12.12.15",
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": {