@juspay/neurolink 9.81.3 → 9.83.0
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 +18 -0
- package/README.md +4 -0
- package/dist/browser/neurolink.min.js +342 -342
- package/dist/cli/commands/proxy.js +5 -0
- package/dist/cli/factories/commandFactory.d.ts +2 -1
- package/dist/cli/factories/commandFactory.js +36 -16
- package/dist/cli/utils/audioPlayer.d.ts +37 -0
- package/dist/cli/utils/audioPlayer.js +138 -0
- package/dist/core/modules/GenerationHandler.js +7 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +14 -6
- package/dist/lib/core/modules/GenerationHandler.js +7 -1
- package/dist/lib/index.d.ts +6 -1
- package/dist/lib/index.js +14 -6
- package/dist/lib/providers/googleAiStudio.js +25 -4
- package/dist/lib/providers/googleNativeGemini3.js +22 -2
- package/dist/lib/providers/googleVertex.js +28 -4
- package/dist/lib/proxy/proxyDispatcher.d.ts +5 -0
- package/dist/lib/proxy/proxyDispatcher.js +61 -0
- package/dist/lib/types/common.d.ts +3 -0
- package/dist/lib/types/providers.d.ts +8 -0
- package/dist/lib/utils/pricing.js +144 -26
- package/dist/lib/utils/systemMessages.d.ts +29 -0
- package/dist/lib/utils/systemMessages.js +45 -0
- package/dist/lib/utils/tokenUtils.d.ts +11 -0
- package/dist/lib/utils/tokenUtils.js +33 -2
- package/dist/providers/googleAiStudio.js +25 -4
- package/dist/providers/googleNativeGemini3.js +22 -2
- package/dist/providers/googleVertex.js +28 -4
- package/dist/proxy/proxyDispatcher.d.ts +5 -0
- package/dist/proxy/proxyDispatcher.js +60 -0
- package/dist/types/common.d.ts +3 -0
- package/dist/types/providers.d.ts +8 -0
- package/dist/utils/pricing.js +144 -26
- package/dist/utils/systemMessages.d.ts +29 -0
- package/dist/utils/systemMessages.js +44 -0
- package/dist/utils/tokenUtils.d.ts +11 -0
- package/dist/utils/tokenUtils.js +33 -2
- package/package.json +3 -2
|
@@ -514,6 +514,7 @@ export async function collectStreamChunks(stream) {
|
|
|
514
514
|
const stepFunctionCalls = [];
|
|
515
515
|
let inputTokens = 0;
|
|
516
516
|
let outputTokens = 0;
|
|
517
|
+
let cacheReadTokens = 0;
|
|
517
518
|
for await (const chunk of stream) {
|
|
518
519
|
// Extract raw parts from candidates FIRST
|
|
519
520
|
// This avoids using chunk.text which triggers SDK warning when
|
|
@@ -533,9 +534,18 @@ export async function collectStreamChunks(stream) {
|
|
|
533
534
|
if (usage) {
|
|
534
535
|
inputTokens = Math.max(inputTokens, usage.promptTokenCount || 0);
|
|
535
536
|
outputTokens = Math.max(outputTokens, usage.candidatesTokenCount || 0);
|
|
537
|
+
// cachedContentTokenCount is OVERLAPPING (a subset already inside
|
|
538
|
+
// promptTokenCount). Surface it so the call site subtracts once.
|
|
539
|
+
cacheReadTokens = Math.max(cacheReadTokens, usage.cachedContentTokenCount || 0);
|
|
536
540
|
}
|
|
537
541
|
}
|
|
538
|
-
return {
|
|
542
|
+
return {
|
|
543
|
+
rawResponseParts,
|
|
544
|
+
stepFunctionCalls,
|
|
545
|
+
inputTokens,
|
|
546
|
+
outputTokens,
|
|
547
|
+
cacheReadTokens,
|
|
548
|
+
};
|
|
539
549
|
}
|
|
540
550
|
/**
|
|
541
551
|
* Create a push-based text channel that bridges a background producer
|
|
@@ -627,6 +637,7 @@ export async function collectStreamChunksIncremental(stream, channel) {
|
|
|
627
637
|
const stepFunctionCalls = [];
|
|
628
638
|
let inputTokens = 0;
|
|
629
639
|
let outputTokens = 0;
|
|
640
|
+
let cacheReadTokens = 0;
|
|
630
641
|
for await (const chunk of stream) {
|
|
631
642
|
const chunkRecord = chunk;
|
|
632
643
|
const candidates = chunkRecord.candidates;
|
|
@@ -648,9 +659,18 @@ export async function collectStreamChunksIncremental(stream, channel) {
|
|
|
648
659
|
if (usage) {
|
|
649
660
|
inputTokens = Math.max(inputTokens, usage.promptTokenCount || 0);
|
|
650
661
|
outputTokens = Math.max(outputTokens, usage.candidatesTokenCount || 0);
|
|
662
|
+
// cachedContentTokenCount is OVERLAPPING (a subset already inside
|
|
663
|
+
// promptTokenCount). Surface it so the call site subtracts once.
|
|
664
|
+
cacheReadTokens = Math.max(cacheReadTokens, usage.cachedContentTokenCount || 0);
|
|
651
665
|
}
|
|
652
666
|
}
|
|
653
|
-
return {
|
|
667
|
+
return {
|
|
668
|
+
rawResponseParts,
|
|
669
|
+
stepFunctionCalls,
|
|
670
|
+
inputTokens,
|
|
671
|
+
outputTokens,
|
|
672
|
+
cacheReadTokens,
|
|
673
|
+
};
|
|
654
674
|
}
|
|
655
675
|
/**
|
|
656
676
|
* Extract the thoughtSignature token from raw response parts.
|
|
@@ -1325,6 +1325,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1325
1325
|
// promptTokenCount is typically in the final chunk, candidatesTokenCount accumulates
|
|
1326
1326
|
let totalInputTokens = 0;
|
|
1327
1327
|
let totalOutputTokens = 0;
|
|
1328
|
+
let totalCacheReadTokens = 0;
|
|
1328
1329
|
// Track text parts as they arrive from the SDK so the returned async
|
|
1329
1330
|
// iterable yields multiple chunks instead of a single buffered chunk.
|
|
1330
1331
|
// The CLI's chunk-count smoke test asserts > 1 stream chunks for any
|
|
@@ -1440,6 +1441,10 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1440
1441
|
totalInputTokens = usageMetadata.promptTokenCount;
|
|
1441
1442
|
// Feed the context guard the REAL prompt size of this call.
|
|
1442
1443
|
contextGuard.noteUsage(usageMetadata.promptTokenCount, usageMetadata.candidatesTokenCount ?? 0);
|
|
1444
|
+
// cachedContentTokenCount is OVERLAPPING (a subset already inside
|
|
1445
|
+
// promptTokenCount). Clamp to the prompt count so a later uncached
|
|
1446
|
+
// step resets it to 0 instead of leaving a stale cached value.
|
|
1447
|
+
totalCacheReadTokens = Math.min(usageMetadata.cachedContentTokenCount ?? 0, usageMetadata.promptTokenCount);
|
|
1443
1448
|
}
|
|
1444
1449
|
// Take the latest candidatesTokenCount (accumulates through chunks)
|
|
1445
1450
|
if (usageMetadata.candidatesTokenCount !== undefined &&
|
|
@@ -1891,6 +1896,10 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1891
1896
|
// Filter out final_result from tool calls as it's an internal pattern
|
|
1892
1897
|
const externalToolCalls = allToolCalls.filter((tc) => tc.toolName !== "final_result");
|
|
1893
1898
|
const externalToolExecutions = toolExecutions.filter((te) => te.name !== "final_result");
|
|
1899
|
+
// Gemini promptTokenCount is OVERLAPPING (already includes
|
|
1900
|
+
// cachedContentTokenCount). Subtract once so the cached portion is billed at
|
|
1901
|
+
// the cheaper cacheRead rate without double-counting; total is conserved.
|
|
1902
|
+
const adjustedInputTokens = Math.max(0, totalInputTokens - totalCacheReadTokens);
|
|
1894
1903
|
const result = {
|
|
1895
1904
|
stream: createTextStream(),
|
|
1896
1905
|
provider: this.providerName,
|
|
@@ -1899,9 +1908,12 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1899
1908
|
stopReason,
|
|
1900
1909
|
rawFinishReason: lastFinishReason,
|
|
1901
1910
|
usage: {
|
|
1902
|
-
input:
|
|
1911
|
+
input: adjustedInputTokens,
|
|
1903
1912
|
output: totalOutputTokens,
|
|
1904
|
-
total:
|
|
1913
|
+
total: adjustedInputTokens + totalCacheReadTokens + totalOutputTokens,
|
|
1914
|
+
...(totalCacheReadTokens > 0 && {
|
|
1915
|
+
cacheReadTokens: totalCacheReadTokens,
|
|
1916
|
+
}),
|
|
1905
1917
|
},
|
|
1906
1918
|
toolCalls: externalToolCalls.map((tc) => ({
|
|
1907
1919
|
toolName: tc.toolName,
|
|
@@ -2254,6 +2266,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2254
2266
|
// promptTokenCount is typically in the final chunk, candidatesTokenCount accumulates
|
|
2255
2267
|
let totalInputTokens = 0;
|
|
2256
2268
|
let totalOutputTokens = 0;
|
|
2269
|
+
let totalCacheReadTokens = 0;
|
|
2257
2270
|
// Abort scaffolding (mirrors executeNativeAnthropicStream). The native
|
|
2258
2271
|
// Gemini SDK cancels via config.abortSignal, so drive an internal
|
|
2259
2272
|
// AbortController: the caller's signal and the turn clock's watchdogs
|
|
@@ -2359,6 +2372,10 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2359
2372
|
totalInputTokens = usageMetadata.promptTokenCount;
|
|
2360
2373
|
// Feed the context guard the REAL prompt size of this call.
|
|
2361
2374
|
contextGuard.noteUsage(usageMetadata.promptTokenCount, usageMetadata.candidatesTokenCount ?? 0);
|
|
2375
|
+
// cachedContentTokenCount is OVERLAPPING (a subset already inside
|
|
2376
|
+
// promptTokenCount). Clamp to the prompt count so a later uncached
|
|
2377
|
+
// step resets it to 0 instead of leaving a stale cached value.
|
|
2378
|
+
totalCacheReadTokens = Math.min(usageMetadata.cachedContentTokenCount ?? 0, usageMetadata.promptTokenCount);
|
|
2362
2379
|
}
|
|
2363
2380
|
// Take the latest candidatesTokenCount (accumulates through chunks)
|
|
2364
2381
|
if (usageMetadata.candidatesTokenCount !== undefined &&
|
|
@@ -2789,6 +2806,10 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2789
2806
|
const externalToolCalls = allToolCalls.filter((tc) => tc.toolName !== "final_result");
|
|
2790
2807
|
const externalToolExecutions = toolExecutions.filter((te) => te.name !== "final_result");
|
|
2791
2808
|
// Build EnhancedGenerateResult
|
|
2809
|
+
// Gemini promptTokenCount is OVERLAPPING (already includes
|
|
2810
|
+
// cachedContentTokenCount). Subtract once so the cached portion is billed at
|
|
2811
|
+
// the cheaper cacheRead rate without double-counting; total is conserved.
|
|
2812
|
+
const adjustedInputTokens = Math.max(0, totalInputTokens - totalCacheReadTokens);
|
|
2792
2813
|
const result = {
|
|
2793
2814
|
content: finalText,
|
|
2794
2815
|
provider: this.providerName,
|
|
@@ -2798,9 +2819,12 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2798
2819
|
rawFinishReason: lastFinishReason,
|
|
2799
2820
|
stepsUsed: step,
|
|
2800
2821
|
usage: {
|
|
2801
|
-
input:
|
|
2822
|
+
input: adjustedInputTokens,
|
|
2802
2823
|
output: totalOutputTokens,
|
|
2803
|
-
total:
|
|
2824
|
+
total: adjustedInputTokens + totalCacheReadTokens + totalOutputTokens,
|
|
2825
|
+
...(totalCacheReadTokens > 0 && {
|
|
2826
|
+
cacheReadTokens: totalCacheReadTokens,
|
|
2827
|
+
}),
|
|
2804
2828
|
},
|
|
2805
2829
|
responseTime,
|
|
2806
2830
|
toolsUsed: externalToolCalls.map((tc) => tc.toolName),
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tuned global undici dispatcher for the proxy's upstream forwards.
|
|
3
|
+
*
|
|
4
|
+
* The Claude passthrough (`claudeProxyRoutes.ts`) forwards every request to
|
|
5
|
+
* Anthropic via the global `fetch` → undici global dispatcher. undici keep-alives
|
|
6
|
+
* by default, but with a ~4s idle timeout: between Claude Code turns (the user
|
|
7
|
+
* reads output, a tool runs, the model thinks) the idle socket closes, so the
|
|
8
|
+
* next request opens a brand-new TCP connection. Under sustained use that is a
|
|
9
|
+
* high rate of short-lived outbound flows.
|
|
10
|
+
*
|
|
11
|
+
* On hosts running a socket content-filter (CFIL) — e.g. SentinelOne or
|
|
12
|
+
* GlobalProtect network extensions — every new flow allocates per-flow kernel
|
|
13
|
+
* state, so a high flow rate amplifies any leak in that path. Reusing connections
|
|
14
|
+
* cuts the flow rate sharply, so we install a dispatcher with a longer keep-alive
|
|
15
|
+
* and a bounded, reused connection pool.
|
|
16
|
+
*
|
|
17
|
+
* Everything is overridable via env so it can be tuned (or disabled) per host:
|
|
18
|
+
* NEUROLINK_PROXY_KEEPALIVE=off disable; keep undici defaults
|
|
19
|
+
* NEUROLINK_PROXY_KEEPALIVE_MS idle keep-alive timeout (default 30000)
|
|
20
|
+
* NEUROLINK_PROXY_KEEPALIVE_MAX_MS keep-alive upper bound (default 600000)
|
|
21
|
+
* NEUROLINK_PROXY_MAX_CONNECTIONS max pooled connections per origin (default 64)
|
|
22
|
+
*/
|
|
23
|
+
import { Agent, setGlobalDispatcher } from "undici";
|
|
24
|
+
import { logger } from "../utils/logger.js";
|
|
25
|
+
function readPositiveInt(name, fallback) {
|
|
26
|
+
const raw = process.env[name];
|
|
27
|
+
if (!raw) {
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
const parsed = Number.parseInt(raw, 10);
|
|
31
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
32
|
+
}
|
|
33
|
+
let configured = false;
|
|
34
|
+
/**
|
|
35
|
+
* Install the tuned global undici dispatcher. Idempotent — safe to call once at
|
|
36
|
+
* proxy startup. No-op when `NEUROLINK_PROXY_KEEPALIVE` is off/false/0.
|
|
37
|
+
*/
|
|
38
|
+
export function configureProxyKeepAliveDispatcher() {
|
|
39
|
+
if (configured) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
configured = true;
|
|
43
|
+
const toggle = (process.env.NEUROLINK_PROXY_KEEPALIVE ?? "").toLowerCase();
|
|
44
|
+
if (toggle === "off" || toggle === "false" || toggle === "0") {
|
|
45
|
+
logger.debug("[proxy] keep-alive dispatcher disabled via env");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const keepAliveTimeout = readPositiveInt("NEUROLINK_PROXY_KEEPALIVE_MS", 30_000);
|
|
49
|
+
const keepAliveMaxTimeout = readPositiveInt("NEUROLINK_PROXY_KEEPALIVE_MAX_MS", 600_000);
|
|
50
|
+
const connections = readPositiveInt("NEUROLINK_PROXY_MAX_CONNECTIONS", 64);
|
|
51
|
+
setGlobalDispatcher(new Agent({
|
|
52
|
+
keepAliveTimeout,
|
|
53
|
+
keepAliveMaxTimeout,
|
|
54
|
+
connections,
|
|
55
|
+
pipelining: 1,
|
|
56
|
+
}));
|
|
57
|
+
logger.debug(`[proxy] tuned undici dispatcher installed ` +
|
|
58
|
+
`(keepAliveTimeout=${keepAliveTimeout}ms, ` +
|
|
59
|
+
`keepAliveMaxTimeout=${keepAliveMaxTimeout}ms, connections=${connections})`);
|
|
60
|
+
}
|
package/dist/types/common.d.ts
CHANGED
|
@@ -223,6 +223,9 @@ export type RawUsageObject = {
|
|
|
223
223
|
cacheReadInputTokens?: number;
|
|
224
224
|
cacheCreationTokens?: number;
|
|
225
225
|
cacheReadTokens?: number;
|
|
226
|
+
prompt_tokens_details?: {
|
|
227
|
+
cached_tokens?: number;
|
|
228
|
+
};
|
|
226
229
|
reasoningTokens?: number;
|
|
227
230
|
reasoning?: number;
|
|
228
231
|
reasoning_tokens?: number;
|
|
@@ -1597,6 +1597,14 @@ export type CollectedChunkResult = {
|
|
|
1597
1597
|
stepFunctionCalls: NativeFunctionCall[];
|
|
1598
1598
|
inputTokens: number;
|
|
1599
1599
|
outputTokens: number;
|
|
1600
|
+
/**
|
|
1601
|
+
* Gemini cached-content tokens (overlapping: included in promptTokenCount).
|
|
1602
|
+
* Surfaced so the call site can subtract from input and bill at cacheRead
|
|
1603
|
+
* rate. Subtraction happens at the call site, not in the collector.
|
|
1604
|
+
*/
|
|
1605
|
+
cacheReadTokens?: number;
|
|
1606
|
+
/** Cache creation tokens (symmetry; Gemini does not emit this). */
|
|
1607
|
+
cacheCreationTokens?: number;
|
|
1600
1608
|
};
|
|
1601
1609
|
/** Push-based text channel for incremental streaming. */
|
|
1602
1610
|
export type TextChannel = {
|
package/dist/utils/pricing.js
CHANGED
|
@@ -87,8 +87,18 @@ const PRICING = {
|
|
|
87
87
|
cacheRead: 1.5 / 1_000_000,
|
|
88
88
|
cacheCreation: 18.75 / 1_000_000,
|
|
89
89
|
},
|
|
90
|
-
"claude-3-sonnet": {
|
|
91
|
-
|
|
90
|
+
"claude-3-sonnet": {
|
|
91
|
+
input: 3.0 / 1_000_000,
|
|
92
|
+
output: 15.0 / 1_000_000,
|
|
93
|
+
cacheRead: 0.3 / 1_000_000,
|
|
94
|
+
cacheCreation: 3.75 / 1_000_000,
|
|
95
|
+
},
|
|
96
|
+
"claude-3-haiku": {
|
|
97
|
+
input: 0.25 / 1_000_000,
|
|
98
|
+
output: 1.25 / 1_000_000,
|
|
99
|
+
cacheRead: 0.025 / 1_000_000,
|
|
100
|
+
cacheCreation: 0.3125 / 1_000_000,
|
|
101
|
+
},
|
|
92
102
|
},
|
|
93
103
|
// Google Vertex AI — Claude models on Vertex (same pricing, @ date suffix)
|
|
94
104
|
vertex: {
|
|
@@ -138,26 +148,95 @@ const PRICING = {
|
|
|
138
148
|
// OpenAI — updated March 2026
|
|
139
149
|
openai: {
|
|
140
150
|
// GPT-5.x family
|
|
141
|
-
|
|
142
|
-
"gpt-5.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
"gpt-5
|
|
151
|
+
// cacheRead = 0.25x input (cached input tokens; no separate cacheCreation).
|
|
152
|
+
"gpt-5.4": {
|
|
153
|
+
input: 2.5 / 1_000_000,
|
|
154
|
+
output: 15.0 / 1_000_000,
|
|
155
|
+
cacheRead: 0.625 / 1_000_000,
|
|
156
|
+
},
|
|
157
|
+
"gpt-5.2": {
|
|
158
|
+
input: 1.75 / 1_000_000,
|
|
159
|
+
output: 14.0 / 1_000_000,
|
|
160
|
+
cacheRead: 0.4375 / 1_000_000,
|
|
161
|
+
},
|
|
162
|
+
"gpt-5.1": {
|
|
163
|
+
input: 0.625 / 1_000_000,
|
|
164
|
+
output: 5.0 / 1_000_000,
|
|
165
|
+
cacheRead: 0.15625 / 1_000_000,
|
|
166
|
+
},
|
|
167
|
+
"gpt-5.1-codex": {
|
|
168
|
+
input: 1.25 / 1_000_000,
|
|
169
|
+
output: 10.0 / 1_000_000,
|
|
170
|
+
cacheRead: 0.3125 / 1_000_000,
|
|
171
|
+
},
|
|
172
|
+
"gpt-5": {
|
|
173
|
+
input: 1.25 / 1_000_000,
|
|
174
|
+
output: 10.0 / 1_000_000,
|
|
175
|
+
cacheRead: 0.3125 / 1_000_000,
|
|
176
|
+
},
|
|
177
|
+
"gpt-5-mini": {
|
|
178
|
+
input: 0.25 / 1_000_000,
|
|
179
|
+
output: 2.0 / 1_000_000,
|
|
180
|
+
cacheRead: 0.0625 / 1_000_000,
|
|
181
|
+
},
|
|
182
|
+
"gpt-5-nano": {
|
|
183
|
+
input: 0.05 / 1_000_000,
|
|
184
|
+
output: 0.4 / 1_000_000,
|
|
185
|
+
cacheRead: 0.0125 / 1_000_000,
|
|
186
|
+
},
|
|
148
187
|
// GPT-4.1 family
|
|
149
|
-
"gpt-4.1": {
|
|
150
|
-
|
|
151
|
-
|
|
188
|
+
"gpt-4.1": {
|
|
189
|
+
input: 2.0 / 1_000_000,
|
|
190
|
+
output: 8.0 / 1_000_000,
|
|
191
|
+
cacheRead: 0.5 / 1_000_000,
|
|
192
|
+
},
|
|
193
|
+
"gpt-4.1-mini": {
|
|
194
|
+
input: 0.4 / 1_000_000,
|
|
195
|
+
output: 1.6 / 1_000_000,
|
|
196
|
+
cacheRead: 0.1 / 1_000_000,
|
|
197
|
+
},
|
|
198
|
+
"gpt-4.1-nano": {
|
|
199
|
+
input: 0.1 / 1_000_000,
|
|
200
|
+
output: 0.4 / 1_000_000,
|
|
201
|
+
cacheRead: 0.025 / 1_000_000,
|
|
202
|
+
},
|
|
152
203
|
// GPT-4o family
|
|
153
|
-
"gpt-4o": {
|
|
154
|
-
|
|
204
|
+
"gpt-4o": {
|
|
205
|
+
input: 2.5 / 1_000_000,
|
|
206
|
+
output: 10.0 / 1_000_000,
|
|
207
|
+
cacheRead: 0.625 / 1_000_000,
|
|
208
|
+
},
|
|
209
|
+
"gpt-4o-mini": {
|
|
210
|
+
input: 0.15 / 1_000_000,
|
|
211
|
+
output: 0.6 / 1_000_000,
|
|
212
|
+
cacheRead: 0.0375 / 1_000_000,
|
|
213
|
+
},
|
|
155
214
|
// o-series reasoning
|
|
156
|
-
o3: {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
215
|
+
o3: {
|
|
216
|
+
input: 2.0 / 1_000_000,
|
|
217
|
+
output: 8.0 / 1_000_000,
|
|
218
|
+
cacheRead: 0.5 / 1_000_000,
|
|
219
|
+
},
|
|
220
|
+
"o3-mini": {
|
|
221
|
+
input: 1.1 / 1_000_000,
|
|
222
|
+
output: 4.4 / 1_000_000,
|
|
223
|
+
cacheRead: 0.275 / 1_000_000,
|
|
224
|
+
},
|
|
225
|
+
"o4-mini": {
|
|
226
|
+
input: 1.1 / 1_000_000,
|
|
227
|
+
output: 4.4 / 1_000_000,
|
|
228
|
+
cacheRead: 0.275 / 1_000_000,
|
|
229
|
+
},
|
|
230
|
+
o1: {
|
|
231
|
+
input: 15.0 / 1_000_000,
|
|
232
|
+
output: 60.0 / 1_000_000,
|
|
233
|
+
cacheRead: 3.75 / 1_000_000,
|
|
234
|
+
},
|
|
235
|
+
"o1-mini": {
|
|
236
|
+
input: 0.55 / 1_000_000,
|
|
237
|
+
output: 2.2 / 1_000_000,
|
|
238
|
+
cacheRead: 0.1375 / 1_000_000,
|
|
239
|
+
},
|
|
161
240
|
// Legacy
|
|
162
241
|
"gpt-4-turbo": { input: 10.0 / 1_000_000, output: 30.0 / 1_000_000 },
|
|
163
242
|
"gpt-4": { input: 30.0 / 1_000_000, output: 60.0 / 1_000_000 },
|
|
@@ -166,52 +245,83 @@ const PRICING = {
|
|
|
166
245
|
// Google (Gemini) — updated March 2026
|
|
167
246
|
google: {
|
|
168
247
|
// Gemini 3.1 family (all require -preview suffix)
|
|
248
|
+
// cacheRead = 0.25x input (cached content tokens; explicit-cache storage is
|
|
249
|
+
// billed separately by TTL/time, so no per-token cacheCreation rate here).
|
|
169
250
|
"gemini-3.1-pro-preview": {
|
|
170
251
|
input: 2.0 / 1_000_000,
|
|
171
252
|
output: 12.0 / 1_000_000,
|
|
253
|
+
cacheRead: 0.5 / 1_000_000,
|
|
172
254
|
},
|
|
173
255
|
"gemini-3.1-flash-lite-preview": {
|
|
174
256
|
input: 0.25 / 1_000_000,
|
|
175
257
|
output: 1.5 / 1_000_000,
|
|
258
|
+
cacheRead: 0.0625 / 1_000_000,
|
|
176
259
|
},
|
|
177
260
|
"gemini-3.1-flash-image-preview": {
|
|
178
261
|
input: 0.5 / 1_000_000,
|
|
179
262
|
output: 3.0 / 1_000_000,
|
|
263
|
+
cacheRead: 0.125 / 1_000_000,
|
|
180
264
|
},
|
|
181
265
|
"gemini-3.1-pro-preview-customtools": {
|
|
182
266
|
input: 2.0 / 1_000_000,
|
|
183
267
|
output: 12.0 / 1_000_000,
|
|
268
|
+
cacheRead: 0.5 / 1_000_000,
|
|
184
269
|
},
|
|
185
270
|
// Gemini 3 family
|
|
186
271
|
"gemini-3-flash-preview": {
|
|
187
272
|
input: 0.5 / 1_000_000,
|
|
188
273
|
output: 3.0 / 1_000_000,
|
|
274
|
+
cacheRead: 0.125 / 1_000_000,
|
|
189
275
|
},
|
|
190
276
|
"gemini-3-pro-image-preview": {
|
|
191
277
|
input: 2.0 / 1_000_000,
|
|
192
278
|
output: 12.0 / 1_000_000,
|
|
279
|
+
cacheRead: 0.5 / 1_000_000,
|
|
193
280
|
},
|
|
194
281
|
/** @deprecated SHUT DOWN March 9, 2026. Migrate to gemini-3.1-pro-preview. */
|
|
195
282
|
"gemini-3-pro-preview": {
|
|
196
283
|
input: 2.0 / 1_000_000,
|
|
197
284
|
output: 12.0 / 1_000_000,
|
|
285
|
+
cacheRead: 0.5 / 1_000_000,
|
|
198
286
|
},
|
|
199
287
|
// Gemini 2.5 family
|
|
200
|
-
"gemini-2.5-flash": {
|
|
201
|
-
|
|
288
|
+
"gemini-2.5-flash": {
|
|
289
|
+
input: 0.3 / 1_000_000,
|
|
290
|
+
output: 2.5 / 1_000_000,
|
|
291
|
+
cacheRead: 0.075 / 1_000_000,
|
|
292
|
+
},
|
|
293
|
+
"gemini-2.5-pro": {
|
|
294
|
+
input: 1.25 / 1_000_000,
|
|
295
|
+
output: 10.0 / 1_000_000,
|
|
296
|
+
cacheRead: 0.3125 / 1_000_000,
|
|
297
|
+
},
|
|
202
298
|
"gemini-2.5-flash-lite": {
|
|
203
299
|
input: 0.1 / 1_000_000,
|
|
204
300
|
output: 0.4 / 1_000_000,
|
|
301
|
+
cacheRead: 0.025 / 1_000_000,
|
|
205
302
|
},
|
|
206
303
|
// Gemini 2.0 family (deprecated June 2026)
|
|
207
|
-
"gemini-2.0-flash": {
|
|
304
|
+
"gemini-2.0-flash": {
|
|
305
|
+
input: 0.15 / 1_000_000,
|
|
306
|
+
output: 0.6 / 1_000_000,
|
|
307
|
+
cacheRead: 0.0375 / 1_000_000,
|
|
308
|
+
},
|
|
208
309
|
"gemini-2.0-flash-lite": {
|
|
209
310
|
input: 0.075 / 1_000_000,
|
|
210
311
|
output: 0.3 / 1_000_000,
|
|
312
|
+
cacheRead: 0.01875 / 1_000_000,
|
|
211
313
|
},
|
|
212
314
|
// Gemini 1.5 family
|
|
213
|
-
"gemini-1.5-pro": {
|
|
214
|
-
|
|
315
|
+
"gemini-1.5-pro": {
|
|
316
|
+
input: 1.25 / 1_000_000,
|
|
317
|
+
output: 5.0 / 1_000_000,
|
|
318
|
+
cacheRead: 0.3125 / 1_000_000,
|
|
319
|
+
},
|
|
320
|
+
"gemini-1.5-flash": {
|
|
321
|
+
input: 0.075 / 1_000_000,
|
|
322
|
+
output: 0.3 / 1_000_000,
|
|
323
|
+
cacheRead: 0.01875 / 1_000_000,
|
|
324
|
+
},
|
|
215
325
|
},
|
|
216
326
|
// Mistral AI
|
|
217
327
|
mistral: {
|
|
@@ -594,8 +704,16 @@ export function calculateCost(provider, model, usage) {
|
|
|
594
704
|
let cost = 0;
|
|
595
705
|
cost += (usage.input || 0) * rates.input;
|
|
596
706
|
cost += (usage.output || 0) * rates.output;
|
|
597
|
-
if (usage.cacheReadTokens
|
|
598
|
-
|
|
707
|
+
if (usage.cacheReadTokens) {
|
|
708
|
+
// Price cache reads at the discounted cacheRead rate when one exists;
|
|
709
|
+
// otherwise fall back to the full input rate. The overlapping extractor
|
|
710
|
+
// (tokenUtils) moves cached tokens out of `input` into `cacheReadTokens`
|
|
711
|
+
// for OpenAI/Gemini-family providers, so without this fallback a provider
|
|
712
|
+
// that reports cached tokens but has no cacheRead rate would bill those
|
|
713
|
+
// tokens at $0 (silent undercharge). Falling back to the input rate keeps
|
|
714
|
+
// total cost identical to pre-split billing for unpriced providers, while
|
|
715
|
+
// priced providers still get the cheaper cacheRead rate.
|
|
716
|
+
cost += usage.cacheReadTokens * (rates.cacheRead ?? rates.input);
|
|
599
717
|
}
|
|
600
718
|
if (usage.cacheCreationTokens && rates.cacheCreation) {
|
|
601
719
|
cost += usage.cacheCreationTokens * rates.cacheCreation;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ModelMessage, SystemModelMessage } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Partition a built message array into system messages and the rest, so the
|
|
4
|
+
* system prompt can ride `generateText`'s top-level `system` option instead of
|
|
5
|
+
* the `messages` array.
|
|
6
|
+
*
|
|
7
|
+
* The AI SDK deprecates system-role entries inside `messages` (warns by default
|
|
8
|
+
* from ai@6.0.170 via the `allowSystemInMessages` option, rejected by default
|
|
9
|
+
* in v7, flagged as a prompt-injection risk). The `system` option accepts full
|
|
10
|
+
* SystemModelMessage objects, so per-message providerOptions (e.g. the anthropic
|
|
11
|
+
* cacheControl breakpoint set by buildMessagesArray) survive the move. See
|
|
12
|
+
* issue #1024.
|
|
13
|
+
*
|
|
14
|
+
* Order is preserved within both partitions and the input is not mutated.
|
|
15
|
+
*
|
|
16
|
+
* Guard: the partition only runs when it leaves a non-empty `messages` array.
|
|
17
|
+
* If every message is a system message (a system-only priming call) — or there
|
|
18
|
+
* are no system messages at all — the original array is returned untouched with
|
|
19
|
+
* `system: undefined`. The AI SDK rejects an empty `messages` array, and a
|
|
20
|
+
* system-only call has no hoisted form (the SDK also requires a non-system
|
|
21
|
+
* message), so that degenerate case is deliberately left on the old
|
|
22
|
+
* system-in-`messages` path rather than made to throw — it keeps whatever
|
|
23
|
+
* behaviour it had before #1024. The normal system-plus-conversation path is
|
|
24
|
+
* always hoisted.
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractSystemMessages(messages: ModelMessage[]): {
|
|
27
|
+
system: SystemModelMessage[] | undefined;
|
|
28
|
+
messages: ModelMessage[];
|
|
29
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Partition a built message array into system messages and the rest, so the
|
|
3
|
+
* system prompt can ride `generateText`'s top-level `system` option instead of
|
|
4
|
+
* the `messages` array.
|
|
5
|
+
*
|
|
6
|
+
* The AI SDK deprecates system-role entries inside `messages` (warns by default
|
|
7
|
+
* from ai@6.0.170 via the `allowSystemInMessages` option, rejected by default
|
|
8
|
+
* in v7, flagged as a prompt-injection risk). The `system` option accepts full
|
|
9
|
+
* SystemModelMessage objects, so per-message providerOptions (e.g. the anthropic
|
|
10
|
+
* cacheControl breakpoint set by buildMessagesArray) survive the move. See
|
|
11
|
+
* issue #1024.
|
|
12
|
+
*
|
|
13
|
+
* Order is preserved within both partitions and the input is not mutated.
|
|
14
|
+
*
|
|
15
|
+
* Guard: the partition only runs when it leaves a non-empty `messages` array.
|
|
16
|
+
* If every message is a system message (a system-only priming call) — or there
|
|
17
|
+
* are no system messages at all — the original array is returned untouched with
|
|
18
|
+
* `system: undefined`. The AI SDK rejects an empty `messages` array, and a
|
|
19
|
+
* system-only call has no hoisted form (the SDK also requires a non-system
|
|
20
|
+
* message), so that degenerate case is deliberately left on the old
|
|
21
|
+
* system-in-`messages` path rather than made to throw — it keeps whatever
|
|
22
|
+
* behaviour it had before #1024. The normal system-plus-conversation path is
|
|
23
|
+
* always hoisted.
|
|
24
|
+
*/
|
|
25
|
+
export function extractSystemMessages(messages) {
|
|
26
|
+
const system = [];
|
|
27
|
+
const rest = [];
|
|
28
|
+
for (const message of messages) {
|
|
29
|
+
if (message.role === "system") {
|
|
30
|
+
system.push(message);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
rest.push(message);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Only hoist when a non-empty messages array remains. A system-only call (or
|
|
37
|
+
// a no-system array) passes through unchanged: hoisting would empty
|
|
38
|
+
// `messages`, which the SDK rejects, and a system-only call has no compliant
|
|
39
|
+
// hoisted form.
|
|
40
|
+
if (system.length === 0 || rest.length === 0) {
|
|
41
|
+
return { system: undefined, messages };
|
|
42
|
+
}
|
|
43
|
+
return { system, messages: rest };
|
|
44
|
+
}
|
|
@@ -38,6 +38,17 @@ export declare function extractCacheCreationTokens(usage: RawUsageObject): numbe
|
|
|
38
38
|
* Supports: cacheReadInputTokens, cacheReadTokens
|
|
39
39
|
*/
|
|
40
40
|
export declare function extractCacheReadTokens(usage: RawUsageObject): number | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Extract cache read token count from the OVERLAPPING-convention nested path
|
|
43
|
+
* used by OpenAI / DeepSeek / NIM / OpenAI-compatible providers:
|
|
44
|
+
* `usage.prompt_tokens_details.cached_tokens`.
|
|
45
|
+
*
|
|
46
|
+
* Unlike {@link extractCacheReadTokens} (non-overlapping Anthropic/Vertex
|
|
47
|
+
* convention where cache tokens are reported SEPARATELY from input), the value
|
|
48
|
+
* returned here is a SUBSET already included in `prompt_tokens`. Callers that
|
|
49
|
+
* use this value MUST subtract it from `input` to avoid double-counting.
|
|
50
|
+
*/
|
|
51
|
+
export declare function extractCachedInputTokensOverlapping(usage: RawUsageObject): number | undefined;
|
|
41
52
|
/**
|
|
42
53
|
* Calculate cache savings percentage
|
|
43
54
|
*
|
package/dist/utils/tokenUtils.js
CHANGED
|
@@ -101,6 +101,23 @@ export function extractCacheReadTokens(usage) {
|
|
|
101
101
|
}
|
|
102
102
|
return undefined;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Extract cache read token count from the OVERLAPPING-convention nested path
|
|
106
|
+
* used by OpenAI / DeepSeek / NIM / OpenAI-compatible providers:
|
|
107
|
+
* `usage.prompt_tokens_details.cached_tokens`.
|
|
108
|
+
*
|
|
109
|
+
* Unlike {@link extractCacheReadTokens} (non-overlapping Anthropic/Vertex
|
|
110
|
+
* convention where cache tokens are reported SEPARATELY from input), the value
|
|
111
|
+
* returned here is a SUBSET already included in `prompt_tokens`. Callers that
|
|
112
|
+
* use this value MUST subtract it from `input` to avoid double-counting.
|
|
113
|
+
*/
|
|
114
|
+
export function extractCachedInputTokensOverlapping(usage) {
|
|
115
|
+
const cached = usage.prompt_tokens_details?.cached_tokens;
|
|
116
|
+
if (typeof cached === "number" && cached > 0) {
|
|
117
|
+
return cached;
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
104
121
|
/**
|
|
105
122
|
* Calculate cache savings percentage
|
|
106
123
|
*
|
|
@@ -149,13 +166,27 @@ export function extractTokenUsage(result, options = {}) {
|
|
|
149
166
|
// Handle nested usage object (some providers wrap usage in a usage property)
|
|
150
167
|
const usage = result.usage && typeof result.usage === "object" ? result.usage : result;
|
|
151
168
|
// Extract base token counts
|
|
152
|
-
|
|
169
|
+
let input = extractInputTokens(usage);
|
|
153
170
|
const output = extractOutputTokens(usage);
|
|
154
171
|
const total = extractTotalTokens(usage, input, output);
|
|
155
172
|
// Extract optional token fields
|
|
156
173
|
const reasoning = extractReasoningTokens(usage);
|
|
157
174
|
const cacheCreationTokens = extractCacheCreationTokens(usage);
|
|
158
|
-
|
|
175
|
+
let cacheReadTokens = extractCacheReadTokens(usage);
|
|
176
|
+
// Overlapping-convention fallback (OpenAI/DeepSeek/NIM/OpenAI-compatible):
|
|
177
|
+
// when no non-overlapping cache-read field was present, look for the nested
|
|
178
|
+
// `prompt_tokens_details.cached_tokens`. That value is a SUBSET already
|
|
179
|
+
// included in `input`, so we must subtract it from `input` to avoid
|
|
180
|
+
// double-counting (and to let calculateCost apply the cheaper cacheRead rate
|
|
181
|
+
// to the cached portion). Only do this when cached <= input so a malformed
|
|
182
|
+
// response can never produce negative input or inflate the total.
|
|
183
|
+
if (cacheReadTokens === undefined) {
|
|
184
|
+
const overlappingCached = extractCachedInputTokensOverlapping(usage);
|
|
185
|
+
if (overlappingCached !== undefined && overlappingCached <= input) {
|
|
186
|
+
cacheReadTokens = overlappingCached;
|
|
187
|
+
input = Math.max(0, input - overlappingCached);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
159
190
|
// Calculate cache savings if enabled
|
|
160
191
|
const cacheSavingsPercent = calculateCacheSavings
|
|
161
192
|
? calculateCacheSavingsPercent(cacheReadTokens, input)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.83.0",
|
|
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": {
|
|
@@ -135,9 +135,10 @@
|
|
|
135
135
|
"test:unit:vitest": "pnpm exec vitest run test/toolRouting.test.ts",
|
|
136
136
|
"test:tool-routing": "pnpm run test:unit:vitest && npx tsx test/continuous-test-suite-tool-routing.ts",
|
|
137
137
|
"test:classifier-router": "npx tsx test/continuous-test-suite-classifier-router.ts",
|
|
138
|
+
"test:system-messages:vitest": "pnpm exec vitest run test/systemMessages.test.ts",
|
|
138
139
|
"test:tool-routing-semantic:vitest": "pnpm exec vitest run test/toolRoutingSemantic.test.ts",
|
|
139
140
|
"test:tool-routing-semantic": "pnpm run test:tool-routing-semantic:vitest && npx tsx test/continuous-test-suite-tool-routing-semantic.ts",
|
|
140
|
-
"test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:unit:vitest && pnpm run test:tool-routing-cli:vitest && pnpm run test:tool-dedup:vitest && pnpm run test:model-pool:vitest && pnpm run test:tool-routing-semantic:vitest && pnpm run test:anthropic-tools-policy && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop",
|
|
141
|
+
"test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:unit:vitest && pnpm run test:tool-routing-cli:vitest && pnpm run test:tool-dedup:vitest && pnpm run test:model-pool:vitest && pnpm run test:system-messages:vitest && pnpm run test:tool-routing-semantic:vitest && pnpm run test:anthropic-tools-policy && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop",
|
|
141
142
|
"// CI tier — live providers, runs only when API keys are present (test:credentials and test:dynamic make real provider calls when keys are set, so they live here, not in test:unit)": "",
|
|
142
143
|
"test:live": "pnpm run test:providers && pnpm run test:mcp:http && pnpm run test:mcp:sdk && pnpm run test:mcp:cli && pnpm run test:observability && pnpm run test:context && pnpm run test:memory && pnpm run test:tool-reliability && pnpm run test:evaluation && pnpm run test:autoresearch && pnpm run test:credentials && pnpm run test:dynamic",
|
|
143
144
|
"// CI tier — product output (image/video/TTS/PPT) — costs $$ per run": "",
|