@juspay/neurolink 9.86.0 → 9.86.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/CHANGELOG.md +12 -0
- package/dist/browser/neurolink.min.js +370 -370
- package/dist/lib/providers/anthropic.js +52 -3
- package/dist/lib/providers/openaiChatCompletionsClient.d.ts +3 -11
- package/dist/lib/proxy/accountQuota.d.ts +5 -0
- package/dist/lib/proxy/accountQuota.js +8 -1
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +22 -6
- package/dist/lib/server/routes/claudeProxyRoutes.js +52 -8
- package/dist/lib/types/common.d.ts +17 -0
- package/dist/lib/utils/anthropicCacheBreakpoints.d.ts +29 -1
- package/dist/lib/utils/anthropicCacheBreakpoints.js +54 -1
- package/dist/lib/utils/tokenUtils.d.ts +7 -2
- package/dist/lib/utils/tokenUtils.js +19 -2
- package/dist/providers/anthropic.js +52 -3
- package/dist/providers/openaiChatCompletionsClient.d.ts +3 -11
- package/dist/proxy/accountQuota.d.ts +5 -0
- package/dist/proxy/accountQuota.js +8 -1
- package/dist/server/routes/claudeProxyRoutes.d.ts +22 -6
- package/dist/server/routes/claudeProxyRoutes.js +52 -8
- package/dist/types/common.d.ts +17 -0
- package/dist/utils/anthropicCacheBreakpoints.d.ts +29 -1
- package/dist/utils/anthropicCacheBreakpoints.js +54 -1
- package/dist/utils/tokenUtils.d.ts +7 -2
- package/dist/utils/tokenUtils.js +19 -2
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Nothing here imports from "ai" or "@ai-sdk/*". The whole point of this
|
|
14
14
|
* module is to be the native replacement for the AI SDK's OpenAI wrapper.
|
|
15
15
|
*/
|
|
16
|
-
import type { OpenAICompatBuildBodyArgs, OpenAICompatChatMessage, OpenAICompatChatRequest, OpenAICompatChatTool, OpenAICompatMessage, OpenAICompatMessageContent, OpenAICompatResponseFormat, OpenAICompatSSEResult, OpenAICompatStreamChunk, OpenAICompatToolChoiceWire, OpenAICompatV3CallToolChoice, OpenAICompatV3CallTools, Tool } from "../types/index.js";
|
|
16
|
+
import type { OpenAICompatBuildBodyArgs, OpenAICompatChatMessage, OpenAICompatChatRequest, OpenAICompatChatTool, OpenAICompatMessage, OpenAICompatMessageContent, OpenAICompatResponseFormat, OpenAICompatSSEResult, OpenAICompatStreamChunk, OpenAICompatToolChoiceWire, OpenAICompatV3CallToolChoice, OpenAICompatV3CallTools, DeferredUsage, Tool } from "../types/index.js";
|
|
17
17
|
export declare const stripTrailingSlash: (s: string) => string;
|
|
18
18
|
export declare const safeStringify: (value: unknown) => string;
|
|
19
19
|
export declare const stringifyToolInput: (input: unknown) => string;
|
|
@@ -38,17 +38,9 @@ export declare const buildBody: (args: OpenAICompatBuildBodyArgs) => OpenAICompa
|
|
|
38
38
|
export declare const parseSSEStream: (body: ReadableStream<Uint8Array>, onTextDelta: (delta: string) => void, onReasoningDelta?: (delta: string) => void) => Promise<OpenAICompatSSEResult>;
|
|
39
39
|
export declare const buildAPIError: (url: string, body: OpenAICompatChatRequest, res: Response) => Promise<Error>;
|
|
40
40
|
export declare const createDeferredAnalytics: () => {
|
|
41
|
-
usagePromise: Promise<
|
|
42
|
-
promptTokens: number;
|
|
43
|
-
completionTokens: number;
|
|
44
|
-
totalTokens: number;
|
|
45
|
-
}>;
|
|
41
|
+
usagePromise: Promise<DeferredUsage>;
|
|
46
42
|
finishPromise: Promise<string>;
|
|
47
|
-
resolveUsage: (u:
|
|
48
|
-
promptTokens: number;
|
|
49
|
-
completionTokens: number;
|
|
50
|
-
totalTokens: number;
|
|
51
|
-
}) => void;
|
|
43
|
+
resolveUsage: (u: DeferredUsage) => void;
|
|
52
44
|
resolveFinish: (reason: string) => void;
|
|
53
45
|
};
|
|
54
46
|
export declare const createChunkQueue: () => {
|
|
@@ -35,5 +35,10 @@ export declare function loadAccountQuota(accountKey: string): Promise<AccountQuo
|
|
|
35
35
|
* Update quota for a single account.
|
|
36
36
|
* Updates in-memory cache immediately (non-blocking),
|
|
37
37
|
* then debounces the disk write to every 5 seconds.
|
|
38
|
+
*
|
|
39
|
+
* Loads the persisted file into the cache before the first write so a save
|
|
40
|
+
* after a process restart merges with existing entries instead of rewriting
|
|
41
|
+
* the file with only the accounts used since boot (which silently erased
|
|
42
|
+
* other accounts' snapshots and blinded quota-aware routing to them).
|
|
38
43
|
*/
|
|
39
44
|
export declare function saveAccountQuota(accountKey: string, quota: AccountQuota): Promise<void>;
|
|
@@ -174,10 +174,17 @@ export async function loadAccountQuota(accountKey) {
|
|
|
174
174
|
* Update quota for a single account.
|
|
175
175
|
* Updates in-memory cache immediately (non-blocking),
|
|
176
176
|
* then debounces the disk write to every 5 seconds.
|
|
177
|
+
*
|
|
178
|
+
* Loads the persisted file into the cache before the first write so a save
|
|
179
|
+
* after a process restart merges with existing entries instead of rewriting
|
|
180
|
+
* the file with only the accounts used since boot (which silently erased
|
|
181
|
+
* other accounts' snapshots and blinded quota-aware routing to them).
|
|
177
182
|
*/
|
|
178
183
|
export async function saveAccountQuota(accountKey, quota) {
|
|
184
|
+
if (!cacheLoaded) {
|
|
185
|
+
await loadAccountQuotas();
|
|
186
|
+
}
|
|
179
187
|
memoryCache[accountKey] = quota;
|
|
180
|
-
cacheLoaded = true;
|
|
181
188
|
dirty = true;
|
|
182
189
|
scheduleFlush();
|
|
183
190
|
}
|
|
@@ -44,18 +44,32 @@ declare function resetEpochToMs(resetEpoch: number | undefined, now: number): nu
|
|
|
44
44
|
* allow a couple of jittered same-account retries, then a short cooldown.
|
|
45
45
|
*/
|
|
46
46
|
declare function planCooldownFor429(quota: AccountQuota | null, retryAfterMs: number, now: number): AccountCooldownPlan;
|
|
47
|
+
/**
|
|
48
|
+
* Seed each account's runtime quota from the persisted snapshots in
|
|
49
|
+
* ~/.neurolink/account-quotas.json (keyed by label). Runtime state is
|
|
50
|
+
* in-memory only, so without this the quota-aware ordering is blind after a
|
|
51
|
+
* proxy restart: all accounts tie, selection falls back to token-store
|
|
52
|
+
* enumeration order, and the first account served becomes self-reinforcing
|
|
53
|
+
* (it alone has data) — starving the others regardless of their resets.
|
|
54
|
+
* Never overwrites fresher in-memory quota; stale disk snapshots degrade
|
|
55
|
+
* gracefully because past reset timestamps are ignored by resetEpochToMs.
|
|
56
|
+
*/
|
|
57
|
+
declare function seedRuntimeQuotasFromDisk(accounts: ProxyPassthroughAccount[]): Promise<void>;
|
|
47
58
|
/**
|
|
48
59
|
* Order accounts to MAXIMIZE quota utilization (fill-first, smart order):
|
|
49
60
|
* spend the account whose window refreshes SOONEST first, so its about-to-reset
|
|
50
61
|
* allowance isn't wasted, then move to accounts with longer-dated resets.
|
|
51
62
|
*
|
|
52
63
|
* Priority among usable accounts:
|
|
53
|
-
* 1.
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
64
|
+
* 1. no quota data yet — probe first: one request reveals its windows and
|
|
65
|
+
* self-corrects the ordering. (Ranking unknowns last would starve them
|
|
66
|
+
* forever: never picked → never observed → never comparable.)
|
|
67
|
+
* 2. soonest WEEKLY (7d) reset — the scarce, use-it-or-lose-it ceiling
|
|
68
|
+
* 3. soonest SESSION (5h) reset
|
|
69
|
+
* 4. highest weekly utilization — finish off the one closest to done
|
|
70
|
+
* 5. configured primary account, then insertion order
|
|
71
|
+
* Cooling/rejected accounts sort last, soonest-back-to-service first, as
|
|
72
|
+
* last resort.
|
|
59
73
|
*/
|
|
60
74
|
declare function orderAccountsByQuota(accounts: ProxyPassthroughAccount[], now: number): ProxyPassthroughAccount[];
|
|
61
75
|
/**
|
|
@@ -95,6 +109,8 @@ export declare const __testHooks: {
|
|
|
95
109
|
planCooldownFor429: typeof planCooldownFor429;
|
|
96
110
|
orderAccountsByQuota: typeof orderAccountsByQuota;
|
|
97
111
|
resetEpochToMs: typeof resetEpochToMs;
|
|
112
|
+
seedRuntimeQuotasFromDisk: typeof seedRuntimeQuotasFromDisk;
|
|
113
|
+
getAccountRuntimeState: (key: string) => RuntimeAccountState | undefined;
|
|
98
114
|
setConfiguredPrimaryAccountKey: (key: string | undefined) => void;
|
|
99
115
|
getConfiguredPrimaryAccountKey: () => string | undefined;
|
|
100
116
|
setPrimaryAccountIndex: (index: number) => void;
|
|
@@ -13,7 +13,7 @@ import { access, readFile } from "node:fs/promises";
|
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { buildStableClaudeCodeBillingHeader, CLAUDE_CLI_USER_AGENT, CLAUDE_CODE_OAUTH_BETAS, getOrCreateClaudeCodeIdentity, parseClaudeCodeUserId, } from "../../auth/anthropicOAuth.js";
|
|
16
|
-
import { parseQuotaHeaders, saveAccountQuota, } from "../../proxy/accountQuota.js";
|
|
16
|
+
import { loadAccountQuotas, parseQuotaHeaders, saveAccountQuota, } from "../../proxy/accountQuota.js";
|
|
17
17
|
import { buildClaudeError, ClaudeStreamSerializer, generateToolUseId, parseClaudeRequest, serializeClaudeResponse, } from "../../proxy/claudeFormat.js";
|
|
18
18
|
import { buildAnthropicModelsListResponse, buildTranslationOptions, extractText, extractToolArgs, extractUsageFromStreamResult, handleTranslatedJsonRequest, handleTranslatedStreamRequest, hasTranslatedOutput, } from "../../proxy/proxyTranslationEngine.js";
|
|
19
19
|
import { tracers } from "../../telemetry/tracers.js";
|
|
@@ -242,6 +242,30 @@ function maybeCoolFromQuota(state, quota, now) {
|
|
|
242
242
|
logger.always(`[proxy] proactively cooling account (${reason}) ~${minutesUntil(clamped, now)}m from success-response quota (status rejected)`);
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Seed each account's runtime quota from the persisted snapshots in
|
|
247
|
+
* ~/.neurolink/account-quotas.json (keyed by label). Runtime state is
|
|
248
|
+
* in-memory only, so without this the quota-aware ordering is blind after a
|
|
249
|
+
* proxy restart: all accounts tie, selection falls back to token-store
|
|
250
|
+
* enumeration order, and the first account served becomes self-reinforcing
|
|
251
|
+
* (it alone has data) — starving the others regardless of their resets.
|
|
252
|
+
* Never overwrites fresher in-memory quota; stale disk snapshots degrade
|
|
253
|
+
* gracefully because past reset timestamps are ignored by resetEpochToMs.
|
|
254
|
+
*/
|
|
255
|
+
async function seedRuntimeQuotasFromDisk(accounts) {
|
|
256
|
+
try {
|
|
257
|
+
const persisted = await loadAccountQuotas();
|
|
258
|
+
for (const account of accounts) {
|
|
259
|
+
const state = getOrCreateRuntimeState(account.key);
|
|
260
|
+
if (!state.quota && persisted[account.label]) {
|
|
261
|
+
state.quota = persisted[account.label];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Non-fatal: seeding is best-effort; ordering falls back to probe-first.
|
|
267
|
+
}
|
|
268
|
+
}
|
|
245
269
|
/** Quota-aware selection is on by default; disable with
|
|
246
270
|
* NEUROLINK_PROXY_QUOTA_ROUTING=off|false|0. Only affects the fill-first
|
|
247
271
|
* strategy (round-robin keeps strict rotation). */
|
|
@@ -260,6 +284,7 @@ function accountSortMetrics(accountKey, now) {
|
|
|
260
284
|
const sessionRejected = q?.sessionStatus === "rejected" && sessionReset !== undefined;
|
|
261
285
|
return {
|
|
262
286
|
usable: !coolingActive && !weeklyRejected && !sessionRejected,
|
|
287
|
+
hasQuota: !!q,
|
|
263
288
|
coolingUntil: st?.coolingUntil ?? 0,
|
|
264
289
|
weeklyReset: weeklyReset ?? Number.POSITIVE_INFINITY,
|
|
265
290
|
sessionReset: sessionReset ?? Number.POSITIVE_INFINITY,
|
|
@@ -272,14 +297,18 @@ function accountSortMetrics(accountKey, now) {
|
|
|
272
297
|
* allowance isn't wasted, then move to accounts with longer-dated resets.
|
|
273
298
|
*
|
|
274
299
|
* Priority among usable accounts:
|
|
275
|
-
* 1.
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
300
|
+
* 1. no quota data yet — probe first: one request reveals its windows and
|
|
301
|
+
* self-corrects the ordering. (Ranking unknowns last would starve them
|
|
302
|
+
* forever: never picked → never observed → never comparable.)
|
|
303
|
+
* 2. soonest WEEKLY (7d) reset — the scarce, use-it-or-lose-it ceiling
|
|
304
|
+
* 3. soonest SESSION (5h) reset
|
|
305
|
+
* 4. highest weekly utilization — finish off the one closest to done
|
|
306
|
+
* 5. configured primary account, then insertion order
|
|
307
|
+
* Cooling/rejected accounts sort last, soonest-back-to-service first, as
|
|
308
|
+
* last resort.
|
|
281
309
|
*/
|
|
282
310
|
function orderAccountsByQuota(accounts, now) {
|
|
311
|
+
const primaryKey = configuredPrimaryAccountKey;
|
|
283
312
|
return [...accounts].sort((a, b) => {
|
|
284
313
|
const ma = accountSortMetrics(a.key, now);
|
|
285
314
|
const mb = accountSortMetrics(b.key, now);
|
|
@@ -291,13 +320,22 @@ function orderAccountsByQuota(accounts, now) {
|
|
|
291
320
|
const bu = mb.coolingUntil || Number.POSITIVE_INFINITY;
|
|
292
321
|
return au - bu;
|
|
293
322
|
}
|
|
323
|
+
if (ma.hasQuota !== mb.hasQuota) {
|
|
324
|
+
return ma.hasQuota ? 1 : -1;
|
|
325
|
+
}
|
|
294
326
|
if (ma.weeklyReset !== mb.weeklyReset) {
|
|
295
327
|
return ma.weeklyReset - mb.weeklyReset;
|
|
296
328
|
}
|
|
297
329
|
if (ma.sessionReset !== mb.sessionReset) {
|
|
298
330
|
return ma.sessionReset - mb.sessionReset;
|
|
299
331
|
}
|
|
300
|
-
|
|
332
|
+
if (ma.weeklyUsed !== mb.weeklyUsed) {
|
|
333
|
+
return mb.weeklyUsed - ma.weeklyUsed;
|
|
334
|
+
}
|
|
335
|
+
if (primaryKey && (a.key === primaryKey) !== (b.key === primaryKey)) {
|
|
336
|
+
return a.key === primaryKey ? -1 : 1;
|
|
337
|
+
}
|
|
338
|
+
return 0;
|
|
301
339
|
});
|
|
302
340
|
}
|
|
303
341
|
// ---------------------------------------------------------------------------
|
|
@@ -1280,6 +1318,7 @@ async function loadClaudeProxyAccounts(args) {
|
|
|
1280
1318
|
state.lastToken = account.token;
|
|
1281
1319
|
state.lastRefreshToken = account.refreshToken;
|
|
1282
1320
|
}
|
|
1321
|
+
await seedRuntimeQuotasFromDisk(accounts);
|
|
1283
1322
|
const enabledAccounts = accounts.filter((account) => {
|
|
1284
1323
|
return !getOrCreateRuntimeState(account.key).permanentlyDisabled;
|
|
1285
1324
|
});
|
|
@@ -3777,6 +3816,11 @@ export const __testHooks = {
|
|
|
3777
3816
|
planCooldownFor429,
|
|
3778
3817
|
orderAccountsByQuota,
|
|
3779
3818
|
resetEpochToMs,
|
|
3819
|
+
seedRuntimeQuotasFromDisk,
|
|
3820
|
+
getAccountRuntimeState: (key) => {
|
|
3821
|
+
const state = accountRuntimeState.get(key);
|
|
3822
|
+
return state ? { ...state } : undefined;
|
|
3823
|
+
},
|
|
3780
3824
|
setConfiguredPrimaryAccountKey: (key) => {
|
|
3781
3825
|
configuredPrimaryAccountKey = key;
|
|
3782
3826
|
},
|
package/dist/types/common.d.ts
CHANGED
|
@@ -223,6 +223,11 @@ export type RawUsageObject = {
|
|
|
223
223
|
cacheReadInputTokens?: number;
|
|
224
224
|
cacheCreationTokens?: number;
|
|
225
225
|
cacheReadTokens?: number;
|
|
226
|
+
cachedInputTokens?: number;
|
|
227
|
+
inputTokenDetails?: {
|
|
228
|
+
cacheReadTokens?: number;
|
|
229
|
+
cacheWriteTokens?: number;
|
|
230
|
+
};
|
|
226
231
|
prompt_tokens_details?: {
|
|
227
232
|
cached_tokens?: number;
|
|
228
233
|
};
|
|
@@ -232,6 +237,18 @@ export type RawUsageObject = {
|
|
|
232
237
|
thinkingTokens?: number;
|
|
233
238
|
usage?: RawUsageObject;
|
|
234
239
|
};
|
|
240
|
+
/**
|
|
241
|
+
* Aggregated usage resolved by a provider's deferred-analytics pair after a
|
|
242
|
+
* multi-step stream loop ends. The cache fields are optional — only providers
|
|
243
|
+
* with prompt caching (Anthropic) populate them.
|
|
244
|
+
*/
|
|
245
|
+
export type DeferredUsage = {
|
|
246
|
+
promptTokens: number;
|
|
247
|
+
completionTokens: number;
|
|
248
|
+
totalTokens: number;
|
|
249
|
+
cacheReadTokens?: number;
|
|
250
|
+
cacheCreationTokens?: number;
|
|
251
|
+
};
|
|
235
252
|
/**
|
|
236
253
|
* Options for token extraction from raw usage objects.
|
|
237
254
|
*/
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type { VertexAnthropicCacheInput, VertexAnthropicCacheOutput } from "../types/index.js";
|
|
1
|
+
import type { VertexAnthropicCacheInput, VertexAnthropicCacheOutput, VertexAnthropicMessage } from "../types/index.js";
|
|
2
|
+
/** Anthropic allows at most four `cache_control` breakpoints per request. */
|
|
3
|
+
export declare const ANTHROPIC_MAX_CACHE_BREAKPOINTS = 4;
|
|
2
4
|
/**
|
|
3
5
|
* Annotate a native Vertex+Claude request with prompt-cache breakpoints.
|
|
4
6
|
*
|
|
@@ -13,3 +15,29 @@ import type { VertexAnthropicCacheInput, VertexAnthropicCacheOutput } from "../t
|
|
|
13
15
|
* Pure: the inputs are cloned, never mutated.
|
|
14
16
|
*/
|
|
15
17
|
export declare function applyVertexAnthropicCacheBreakpoints(input: VertexAnthropicCacheInput): VertexAnthropicCacheOutput;
|
|
18
|
+
/**
|
|
19
|
+
* Count the `cache_control` markers already present on a request. The direct
|
|
20
|
+
* Anthropic path (AI-SDK pipeline) arrives with markers the upstream layers
|
|
21
|
+
* placed — MessageBuilder tags the system prompt, GenerationHandler tags the
|
|
22
|
+
* last tool definition, and message content blocks may carry translated
|
|
23
|
+
* AI-SDK markers. Anthropic rejects requests with more than four markers, so
|
|
24
|
+
* any additional history breakpoints must fit in the remaining budget.
|
|
25
|
+
*/
|
|
26
|
+
export declare function countAnthropicCacheMarkers(input: {
|
|
27
|
+
system?: string | ReadonlyArray<{
|
|
28
|
+
cache_control?: unknown;
|
|
29
|
+
}> | undefined;
|
|
30
|
+
tools?: ReadonlyArray<{
|
|
31
|
+
cache_control?: unknown;
|
|
32
|
+
}> | undefined;
|
|
33
|
+
messages: ReadonlyArray<VertexAnthropicMessage>;
|
|
34
|
+
}): number;
|
|
35
|
+
/**
|
|
36
|
+
* Rolling history breakpoints for request paths whose stable-prefix markers
|
|
37
|
+
* are managed upstream (direct Anthropic: system via MessageBuilder, last
|
|
38
|
+
* tool via GenerationHandler). Marks the last content block of up to `budget`
|
|
39
|
+
* tail messages; a tail block that already carries a marker is left as-is
|
|
40
|
+
* without consuming budget (it already serves as that breakpoint). Pure —
|
|
41
|
+
* the input array is cloned, never mutated.
|
|
42
|
+
*/
|
|
43
|
+
export declare function applyAnthropicHistoryCacheBreakpoints(input: ReadonlyArray<VertexAnthropicMessage>, budget: number): VertexAnthropicMessage[];
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
const EPHEMERAL = { type: "ephemeral" };
|
|
18
18
|
/** Anthropic allows at most four `cache_control` breakpoints per request. */
|
|
19
|
-
const
|
|
19
|
+
export const ANTHROPIC_MAX_CACHE_BREAKPOINTS = 4;
|
|
20
|
+
const MAX_BREAKPOINTS = ANTHROPIC_MAX_CACHE_BREAKPOINTS;
|
|
20
21
|
/**
|
|
21
22
|
* Annotate a native Vertex+Claude request with prompt-cache breakpoints.
|
|
22
23
|
*
|
|
@@ -60,6 +61,58 @@ export function applyVertexAnthropicCacheBreakpoints(input) {
|
|
|
60
61
|
}
|
|
61
62
|
return { system, tools, messages };
|
|
62
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Count the `cache_control` markers already present on a request. The direct
|
|
66
|
+
* Anthropic path (AI-SDK pipeline) arrives with markers the upstream layers
|
|
67
|
+
* placed — MessageBuilder tags the system prompt, GenerationHandler tags the
|
|
68
|
+
* last tool definition, and message content blocks may carry translated
|
|
69
|
+
* AI-SDK markers. Anthropic rejects requests with more than four markers, so
|
|
70
|
+
* any additional history breakpoints must fit in the remaining budget.
|
|
71
|
+
*/
|
|
72
|
+
export function countAnthropicCacheMarkers(input) {
|
|
73
|
+
let count = 0;
|
|
74
|
+
if (Array.isArray(input.system)) {
|
|
75
|
+
count += input.system.filter((b) => b.cache_control).length;
|
|
76
|
+
}
|
|
77
|
+
if (input.tools) {
|
|
78
|
+
count += input.tools.filter((t) => t.cache_control).length;
|
|
79
|
+
}
|
|
80
|
+
for (const message of input.messages) {
|
|
81
|
+
if (Array.isArray(message.content)) {
|
|
82
|
+
count += message.content.filter((b) => b.cache_control).length;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return count;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Rolling history breakpoints for request paths whose stable-prefix markers
|
|
89
|
+
* are managed upstream (direct Anthropic: system via MessageBuilder, last
|
|
90
|
+
* tool via GenerationHandler). Marks the last content block of up to `budget`
|
|
91
|
+
* tail messages; a tail block that already carries a marker is left as-is
|
|
92
|
+
* without consuming budget (it already serves as that breakpoint). Pure —
|
|
93
|
+
* the input array is cloned, never mutated.
|
|
94
|
+
*/
|
|
95
|
+
export function applyAnthropicHistoryCacheBreakpoints(input, budget) {
|
|
96
|
+
const messages = input.map((m) => ({ ...m }));
|
|
97
|
+
let remaining = Math.max(0, Math.min(budget, MAX_BREAKPOINTS));
|
|
98
|
+
for (let i = messages.length - 1; i >= 0 && remaining > 0; i--) {
|
|
99
|
+
if (lastContentBlockHasMarker(messages[i])) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (markLastContentBlock(messages, i)) {
|
|
103
|
+
remaining--;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return messages;
|
|
107
|
+
}
|
|
108
|
+
/** True when the last content block of a message already carries `cache_control`. */
|
|
109
|
+
function lastContentBlockHasMarker(message) {
|
|
110
|
+
if (!Array.isArray(message.content) || message.content.length === 0) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const last = message.content[message.content.length - 1];
|
|
114
|
+
return !!last.cache_control;
|
|
115
|
+
}
|
|
63
116
|
/**
|
|
64
117
|
* Place a cache breakpoint on the last content block of `messages[i]`.
|
|
65
118
|
* Anthropic attaches `cache_control` to a content block, not the message
|
|
@@ -30,12 +30,17 @@ export declare function extractTotalTokens(usage: RawUsageObject, input: number,
|
|
|
30
30
|
export declare function extractReasoningTokens(usage: RawUsageObject): number | undefined;
|
|
31
31
|
/**
|
|
32
32
|
* Extract cache creation token count from various provider formats
|
|
33
|
-
* Supports: cacheCreationInputTokens, cacheCreationTokens
|
|
33
|
+
* Supports: cacheCreationInputTokens, cacheCreationTokens, and the ai@6
|
|
34
|
+
* normalized `inputTokenDetails.cacheWriteTokens` (the only shape the native
|
|
35
|
+
* direct-Anthropic path reports through generateText).
|
|
34
36
|
*/
|
|
35
37
|
export declare function extractCacheCreationTokens(usage: RawUsageObject): number | undefined;
|
|
36
38
|
/**
|
|
37
39
|
* Extract cache read token count from various provider formats
|
|
38
|
-
* Supports: cacheReadInputTokens, cacheReadTokens
|
|
40
|
+
* Supports: cacheReadInputTokens, cacheReadTokens, and the ai@6 normalized
|
|
41
|
+
* shapes — flat `cachedInputTokens` and nested
|
|
42
|
+
* `inputTokenDetails.cacheReadTokens` (the only shapes the native
|
|
43
|
+
* direct-Anthropic path reports through generateText).
|
|
39
44
|
*/
|
|
40
45
|
export declare function extractCacheReadTokens(usage: RawUsageObject): number | undefined;
|
|
41
46
|
/**
|
package/dist/utils/tokenUtils.js
CHANGED
|
@@ -74,7 +74,9 @@ export function extractReasoningTokens(usage) {
|
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
76
|
* Extract cache creation token count from various provider formats
|
|
77
|
-
* Supports: cacheCreationInputTokens, cacheCreationTokens
|
|
77
|
+
* Supports: cacheCreationInputTokens, cacheCreationTokens, and the ai@6
|
|
78
|
+
* normalized `inputTokenDetails.cacheWriteTokens` (the only shape the native
|
|
79
|
+
* direct-Anthropic path reports through generateText).
|
|
78
80
|
*/
|
|
79
81
|
export function extractCacheCreationTokens(usage) {
|
|
80
82
|
if (typeof usage.cacheCreationInputTokens === "number" &&
|
|
@@ -85,11 +87,18 @@ export function extractCacheCreationTokens(usage) {
|
|
|
85
87
|
usage.cacheCreationTokens > 0) {
|
|
86
88
|
return usage.cacheCreationTokens;
|
|
87
89
|
}
|
|
90
|
+
if (typeof usage.inputTokenDetails?.cacheWriteTokens === "number" &&
|
|
91
|
+
usage.inputTokenDetails.cacheWriteTokens > 0) {
|
|
92
|
+
return usage.inputTokenDetails.cacheWriteTokens;
|
|
93
|
+
}
|
|
88
94
|
return undefined;
|
|
89
95
|
}
|
|
90
96
|
/**
|
|
91
97
|
* Extract cache read token count from various provider formats
|
|
92
|
-
* Supports: cacheReadInputTokens, cacheReadTokens
|
|
98
|
+
* Supports: cacheReadInputTokens, cacheReadTokens, and the ai@6 normalized
|
|
99
|
+
* shapes — flat `cachedInputTokens` and nested
|
|
100
|
+
* `inputTokenDetails.cacheReadTokens` (the only shapes the native
|
|
101
|
+
* direct-Anthropic path reports through generateText).
|
|
93
102
|
*/
|
|
94
103
|
export function extractCacheReadTokens(usage) {
|
|
95
104
|
if (typeof usage.cacheReadInputTokens === "number" &&
|
|
@@ -99,6 +108,14 @@ export function extractCacheReadTokens(usage) {
|
|
|
99
108
|
if (typeof usage.cacheReadTokens === "number" && usage.cacheReadTokens > 0) {
|
|
100
109
|
return usage.cacheReadTokens;
|
|
101
110
|
}
|
|
111
|
+
if (typeof usage.cachedInputTokens === "number" &&
|
|
112
|
+
usage.cachedInputTokens > 0) {
|
|
113
|
+
return usage.cachedInputTokens;
|
|
114
|
+
}
|
|
115
|
+
if (typeof usage.inputTokenDetails?.cacheReadTokens === "number" &&
|
|
116
|
+
usage.inputTokenDetails.cacheReadTokens > 0) {
|
|
117
|
+
return usage.inputTokenDetails.cacheReadTokens;
|
|
118
|
+
}
|
|
102
119
|
return undefined;
|
|
103
120
|
}
|
|
104
121
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.86.
|
|
3
|
+
"version": "9.86.2",
|
|
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": {
|