@juspay/neurolink 9.79.0 → 9.79.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.
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Anthropic prompt-cache breakpoint placement for the native Vertex+Claude
3
+ * request path.
4
+ *
5
+ * Vertex does NOT support automatic prompt caching — the only way the
6
+ * conversation prefix gets cached across turns is explicit `cache_control`
7
+ * markers in the request. Anthropic renders a request as `tools → system →
8
+ * messages` and caches the prefix up to each marker, with a hard ceiling of
9
+ * four markers.
10
+ *
11
+ * Without a marker on the message history, the entire (growing, often
12
+ * tool-result-heavy) conversation falls *after* the last breakpoint and is
13
+ * re-billed at full input price on every turn. That is the regression this
14
+ * fixes: it gives the history a rolling breakpoint so the stable prefix is
15
+ * cached at ~0.1x and only the newest turn is fresh.
16
+ */
17
+ const EPHEMERAL = { type: "ephemeral" };
18
+ /** Anthropic allows at most four `cache_control` breakpoints per request. */
19
+ const MAX_BREAKPOINTS = 4;
20
+ /**
21
+ * Annotate a native Vertex+Claude request with prompt-cache breakpoints.
22
+ *
23
+ * Budget allocation (max 4 markers):
24
+ * 1. The stable prefix — the last system block when a system prompt is
25
+ * present (this single marker caches `tools + system`, since system
26
+ * renders after tools); otherwise the last tool definition.
27
+ * 2-4. A rolling breakpoint on the last few messages, so the
28
+ * growing-but-now-stable conversation history is cached and only the
29
+ * newest turn is billed as fresh input.
30
+ *
31
+ * Pure: the inputs are cloned, never mutated.
32
+ */
33
+ export function applyVertexAnthropicCacheBreakpoints(input) {
34
+ let budget = MAX_BREAKPOINTS;
35
+ // 1. Stable prefix. A bare string `system` cannot carry cache_control, so
36
+ // convert it to block form. Marking the last system block caches the whole
37
+ // tools + system prefix; only fall back to marking the last tool when there
38
+ // is no system prompt to mark.
39
+ let system = input.system;
40
+ let tools = input.tools;
41
+ const hasSystem = !!input.system && input.system.trim().length > 0;
42
+ if (hasSystem) {
43
+ system = [
44
+ { type: "text", text: input.system, cache_control: EPHEMERAL },
45
+ ];
46
+ budget--;
47
+ }
48
+ else if (input.tools && input.tools.length > 0) {
49
+ const lastIndex = input.tools.length - 1;
50
+ tools = input.tools.map((tool, i) => i === lastIndex ? { ...tool, cache_control: EPHEMERAL } : tool);
51
+ budget--;
52
+ }
53
+ // 2. Rolling history breakpoints over the tail of the conversation.
54
+ const messages = input.messages.map((m) => ({ ...m }));
55
+ let remaining = Math.min(budget, input.maxHistoryBreakpoints ?? MAX_BREAKPOINTS);
56
+ for (let i = messages.length - 1; i >= 0 && remaining > 0; i--) {
57
+ if (markLastContentBlock(messages, i)) {
58
+ remaining--;
59
+ }
60
+ }
61
+ return { system, tools, messages };
62
+ }
63
+ /**
64
+ * Place a cache breakpoint on the last content block of `messages[i]`.
65
+ * Anthropic attaches `cache_control` to a content block, not the message
66
+ * envelope, so a string content body is first converted to block form.
67
+ * Returns false when the message has no markable block (caller then walks to
68
+ * an earlier message), so an empty turn never silently consumes a breakpoint.
69
+ */
70
+ function markLastContentBlock(messages, i) {
71
+ const message = messages[i];
72
+ if (typeof message.content === "string") {
73
+ if (message.content.length === 0) {
74
+ return false;
75
+ }
76
+ messages[i] = {
77
+ ...message,
78
+ content: [
79
+ { type: "text", text: message.content, cache_control: EPHEMERAL },
80
+ ],
81
+ };
82
+ return true;
83
+ }
84
+ if (!Array.isArray(message.content) || message.content.length === 0) {
85
+ return false;
86
+ }
87
+ // Shallow clone is sufficient: we only ever add a top-level `cache_control`
88
+ // field to the last block and never mutate nested members (e.g. an image's
89
+ // `source`). Those nested objects stay shared with the input by reference,
90
+ // which is safe because they are never written to. If a future block shape
91
+ // requires mutating nested members, deep-clone that block instead.
92
+ const content = message.content.map((block) => ({ ...block }));
93
+ const lastIndex = content.length - 1;
94
+ content[lastIndex] = { ...content[lastIndex], cache_control: EPHEMERAL };
95
+ messages[i] = { ...message, content };
96
+ return true;
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "9.79.0",
3
+ "version": "9.79.1",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applications with 21+ providers: OpenAI, Anthropic, Google AI Studio, Google Vertex, AWS Bedrock, Azure OpenAI, Mistral, LiteLLM, SageMaker, Hugging Face, Ollama, OpenAI-compatible, OpenRouter, DeepSeek, NVIDIA NIM, LM Studio, llama.cpp, plus voice (OpenAI TTS, ElevenLabs, Deepgram, Azure Speech).",
6
6
  "author": {
@@ -70,6 +70,7 @@
70
70
  "test": "npx tsx test/continuous-test-suite.ts",
71
71
  "test:client": "npx tsx test/continuous-test-suite-client.ts",
72
72
  "test:context": "npx tsx test/continuous-test-suite-context.ts",
73
+ "test:cache": "npx tsx test/continuous-test-suite-cache-breakpoints.ts",
73
74
  "test:evaluation": "npx tsx test/continuous-test-suite-evaluation.ts",
74
75
  "test:mcp": "npx tsx test/continuous-test-suite-mcp-infra.ts",
75
76
  "test:mcp:http": "npx tsx test/continuous-test-suite-mcp-http.ts",