@juspay/neurolink 9.86.1 → 9.86.3
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/README.md +18 -11
- package/dist/browser/neurolink.min.js +351 -351
- package/dist/lib/providers/anthropic.js +52 -3
- package/dist/lib/providers/openaiChatCompletionsClient.d.ts +3 -11
- 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/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 +4 -3
|
@@ -13,6 +13,7 @@ import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
|
13
13
|
import { AuthenticationError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
|
|
14
14
|
import { logger } from "../utils/logger.js";
|
|
15
15
|
import { redactUrlCredentials } from "../utils/logSanitize.js";
|
|
16
|
+
import { ANTHROPIC_MAX_CACHE_BREAKPOINTS, applyAnthropicHistoryCacheBreakpoints, countAnthropicCacheMarkers, } from "../utils/anthropicCacheBreakpoints.js";
|
|
16
17
|
import { calculateCost } from "../utils/pricing.js";
|
|
17
18
|
import { createAnthropicConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
|
|
18
19
|
import { composeAbortSignals, createTimeoutController, mergeAbortSignals, TimeoutError, } from "../utils/timeout.js";
|
|
@@ -1123,9 +1124,23 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1123
1124
|
}
|
|
1124
1125
|
// Extended thinking passthrough (providerOptions.anthropic.thinking).
|
|
1125
1126
|
const thinking = options.providerOptions?.anthropic?.thinking;
|
|
1127
|
+
// Prompt-cache parity with the native Vertex+Claude path: upstream
|
|
1128
|
+
// layers mark only the stable prefix (system via MessageBuilder,
|
|
1129
|
+
// last tool via GenerationHandler) — the growing conversation
|
|
1130
|
+
// history has no breakpoint, so on every turn it falls after the
|
|
1131
|
+
// last marker and is re-billed as fresh input. Add rolling history
|
|
1132
|
+
// breakpoints in whatever budget remains under Anthropic's
|
|
1133
|
+
// four-marker ceiling; pre-existing markers are counted so the
|
|
1134
|
+
// request can never exceed the cap.
|
|
1135
|
+
const cacheMarkersUsed = countAnthropicCacheMarkers({
|
|
1136
|
+
system,
|
|
1137
|
+
tools,
|
|
1138
|
+
messages: messages,
|
|
1139
|
+
});
|
|
1140
|
+
const cachedMessages = applyAnthropicHistoryCacheBreakpoints(messages, ANTHROPIC_MAX_CACHE_BREAKPOINTS - cacheMarkersUsed);
|
|
1126
1141
|
const params = {
|
|
1127
1142
|
model: modelId,
|
|
1128
|
-
messages,
|
|
1143
|
+
messages: cachedMessages,
|
|
1129
1144
|
max_tokens: resolveClaudeMaxTokens(modelId, options.maxOutputTokens),
|
|
1130
1145
|
...(system ? { system } : {}),
|
|
1131
1146
|
...(options.temperature !== undefined &&
|
|
@@ -1345,10 +1360,19 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1345
1360
|
.then((usage) => {
|
|
1346
1361
|
streamSpan.setAttribute("gen_ai.usage.input_tokens", usage.promptTokens || 0);
|
|
1347
1362
|
streamSpan.setAttribute("gen_ai.usage.output_tokens", usage.completionTokens || 0);
|
|
1363
|
+
if (usage.cacheReadTokens) {
|
|
1364
|
+
streamSpan.setAttribute("gen_ai.usage.cached_input_tokens", usage.cacheReadTokens);
|
|
1365
|
+
}
|
|
1348
1366
|
const cost = calculateCost(this.providerName, this.modelName, {
|
|
1349
1367
|
input: usage.promptTokens || 0,
|
|
1350
1368
|
output: usage.completionTokens || 0,
|
|
1351
1369
|
total: usage.totalTokens || 0,
|
|
1370
|
+
...(usage.cacheReadTokens
|
|
1371
|
+
? { cacheReadTokens: usage.cacheReadTokens }
|
|
1372
|
+
: {}),
|
|
1373
|
+
...(usage.cacheCreationTokens
|
|
1374
|
+
? { cacheCreationTokens: usage.cacheCreationTokens }
|
|
1375
|
+
: {}),
|
|
1352
1376
|
});
|
|
1353
1377
|
if (cost && cost > 0) {
|
|
1354
1378
|
streamSpan.setAttribute("neurolink.cost", cost);
|
|
@@ -1372,11 +1396,26 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1372
1396
|
const conversation = payload.messages.slice();
|
|
1373
1397
|
let totalInput = 0;
|
|
1374
1398
|
let totalOutput = 0;
|
|
1399
|
+
let totalCacheRead = 0;
|
|
1400
|
+
let totalCacheWrite = 0;
|
|
1375
1401
|
let lastStop = null;
|
|
1376
1402
|
for (let step = 0; step < maxSteps; step++) {
|
|
1403
|
+
// Prompt-cache parity with the native Vertex+Claude path — rolling
|
|
1404
|
+
// history breakpoints, re-applied per step so the stable prefix
|
|
1405
|
+
// stays byte-identical while the breakpoint follows the growing
|
|
1406
|
+
// tail. Budget respects markers upstream layers already placed
|
|
1407
|
+
// (system / last tool / message blocks) so the request never
|
|
1408
|
+
// exceeds Anthropic's four-marker cap. Pure: `conversation` itself
|
|
1409
|
+
// is never mutated, so re-counting per step stays stable.
|
|
1410
|
+
const cacheMarkersUsed = countAnthropicCacheMarkers({
|
|
1411
|
+
system: payload.system,
|
|
1412
|
+
tools: anthropicTools,
|
|
1413
|
+
messages: conversation,
|
|
1414
|
+
});
|
|
1415
|
+
const cachedConversation = applyAnthropicHistoryCacheBreakpoints(conversation, ANTHROPIC_MAX_CACHE_BREAKPOINTS - cacheMarkersUsed);
|
|
1377
1416
|
const params = {
|
|
1378
1417
|
model: modelId,
|
|
1379
|
-
messages:
|
|
1418
|
+
messages: cachedConversation,
|
|
1380
1419
|
max_tokens: resolveClaudeMaxTokens(modelId, options.maxTokens),
|
|
1381
1420
|
stream: true,
|
|
1382
1421
|
...(payload.system ? { system: payload.system } : {}),
|
|
@@ -1406,6 +1445,12 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1406
1445
|
if (event.type === "message_start") {
|
|
1407
1446
|
totalInput += event.message.usage.input_tokens ?? 0;
|
|
1408
1447
|
totalOutput += event.message.usage.output_tokens ?? 0;
|
|
1448
|
+
// Anthropic reports cache reads/writes SEPARATELY from
|
|
1449
|
+
// input_tokens on the same message_start event — without these
|
|
1450
|
+
// the streaming path silently drops all cache accounting.
|
|
1451
|
+
totalCacheRead += event.message.usage.cache_read_input_tokens ?? 0;
|
|
1452
|
+
totalCacheWrite +=
|
|
1453
|
+
event.message.usage.cache_creation_input_tokens ?? 0;
|
|
1409
1454
|
}
|
|
1410
1455
|
else if (event.type === "content_block_start") {
|
|
1411
1456
|
blockTypes.set(event.index, event.content_block.type);
|
|
@@ -1578,7 +1623,11 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1578
1623
|
resolveUsage({
|
|
1579
1624
|
promptTokens: totalInput,
|
|
1580
1625
|
completionTokens: totalOutput,
|
|
1581
|
-
totalTokens: totalInput + totalOutput,
|
|
1626
|
+
totalTokens: totalInput + totalCacheRead + totalCacheWrite + totalOutput,
|
|
1627
|
+
...(totalCacheRead > 0 ? { cacheReadTokens: totalCacheRead } : {}),
|
|
1628
|
+
...(totalCacheWrite > 0
|
|
1629
|
+
? { cacheCreationTokens: totalCacheWrite }
|
|
1630
|
+
: {}),
|
|
1582
1631
|
});
|
|
1583
1632
|
resolveFinish(lastStop ?? "stop");
|
|
1584
1633
|
};
|
|
@@ -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: () => {
|
|
@@ -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
|
/**
|
|
@@ -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
|
/**
|
|
@@ -13,6 +13,7 @@ import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
|
13
13
|
import { AuthenticationError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
|
|
14
14
|
import { logger } from "../utils/logger.js";
|
|
15
15
|
import { redactUrlCredentials } from "../utils/logSanitize.js";
|
|
16
|
+
import { ANTHROPIC_MAX_CACHE_BREAKPOINTS, applyAnthropicHistoryCacheBreakpoints, countAnthropicCacheMarkers, } from "../utils/anthropicCacheBreakpoints.js";
|
|
16
17
|
import { calculateCost } from "../utils/pricing.js";
|
|
17
18
|
import { createAnthropicConfig, getProviderModel, validateApiKey, } from "../utils/providerConfig.js";
|
|
18
19
|
import { composeAbortSignals, createTimeoutController, mergeAbortSignals, TimeoutError, } from "../utils/timeout.js";
|
|
@@ -1123,9 +1124,23 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1123
1124
|
}
|
|
1124
1125
|
// Extended thinking passthrough (providerOptions.anthropic.thinking).
|
|
1125
1126
|
const thinking = options.providerOptions?.anthropic?.thinking;
|
|
1127
|
+
// Prompt-cache parity with the native Vertex+Claude path: upstream
|
|
1128
|
+
// layers mark only the stable prefix (system via MessageBuilder,
|
|
1129
|
+
// last tool via GenerationHandler) — the growing conversation
|
|
1130
|
+
// history has no breakpoint, so on every turn it falls after the
|
|
1131
|
+
// last marker and is re-billed as fresh input. Add rolling history
|
|
1132
|
+
// breakpoints in whatever budget remains under Anthropic's
|
|
1133
|
+
// four-marker ceiling; pre-existing markers are counted so the
|
|
1134
|
+
// request can never exceed the cap.
|
|
1135
|
+
const cacheMarkersUsed = countAnthropicCacheMarkers({
|
|
1136
|
+
system,
|
|
1137
|
+
tools,
|
|
1138
|
+
messages: messages,
|
|
1139
|
+
});
|
|
1140
|
+
const cachedMessages = applyAnthropicHistoryCacheBreakpoints(messages, ANTHROPIC_MAX_CACHE_BREAKPOINTS - cacheMarkersUsed);
|
|
1126
1141
|
const params = {
|
|
1127
1142
|
model: modelId,
|
|
1128
|
-
messages,
|
|
1143
|
+
messages: cachedMessages,
|
|
1129
1144
|
max_tokens: resolveClaudeMaxTokens(modelId, options.maxOutputTokens),
|
|
1130
1145
|
...(system ? { system } : {}),
|
|
1131
1146
|
...(options.temperature !== undefined &&
|
|
@@ -1345,10 +1360,19 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1345
1360
|
.then((usage) => {
|
|
1346
1361
|
streamSpan.setAttribute("gen_ai.usage.input_tokens", usage.promptTokens || 0);
|
|
1347
1362
|
streamSpan.setAttribute("gen_ai.usage.output_tokens", usage.completionTokens || 0);
|
|
1363
|
+
if (usage.cacheReadTokens) {
|
|
1364
|
+
streamSpan.setAttribute("gen_ai.usage.cached_input_tokens", usage.cacheReadTokens);
|
|
1365
|
+
}
|
|
1348
1366
|
const cost = calculateCost(this.providerName, this.modelName, {
|
|
1349
1367
|
input: usage.promptTokens || 0,
|
|
1350
1368
|
output: usage.completionTokens || 0,
|
|
1351
1369
|
total: usage.totalTokens || 0,
|
|
1370
|
+
...(usage.cacheReadTokens
|
|
1371
|
+
? { cacheReadTokens: usage.cacheReadTokens }
|
|
1372
|
+
: {}),
|
|
1373
|
+
...(usage.cacheCreationTokens
|
|
1374
|
+
? { cacheCreationTokens: usage.cacheCreationTokens }
|
|
1375
|
+
: {}),
|
|
1352
1376
|
});
|
|
1353
1377
|
if (cost && cost > 0) {
|
|
1354
1378
|
streamSpan.setAttribute("neurolink.cost", cost);
|
|
@@ -1372,11 +1396,26 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1372
1396
|
const conversation = payload.messages.slice();
|
|
1373
1397
|
let totalInput = 0;
|
|
1374
1398
|
let totalOutput = 0;
|
|
1399
|
+
let totalCacheRead = 0;
|
|
1400
|
+
let totalCacheWrite = 0;
|
|
1375
1401
|
let lastStop = null;
|
|
1376
1402
|
for (let step = 0; step < maxSteps; step++) {
|
|
1403
|
+
// Prompt-cache parity with the native Vertex+Claude path — rolling
|
|
1404
|
+
// history breakpoints, re-applied per step so the stable prefix
|
|
1405
|
+
// stays byte-identical while the breakpoint follows the growing
|
|
1406
|
+
// tail. Budget respects markers upstream layers already placed
|
|
1407
|
+
// (system / last tool / message blocks) so the request never
|
|
1408
|
+
// exceeds Anthropic's four-marker cap. Pure: `conversation` itself
|
|
1409
|
+
// is never mutated, so re-counting per step stays stable.
|
|
1410
|
+
const cacheMarkersUsed = countAnthropicCacheMarkers({
|
|
1411
|
+
system: payload.system,
|
|
1412
|
+
tools: anthropicTools,
|
|
1413
|
+
messages: conversation,
|
|
1414
|
+
});
|
|
1415
|
+
const cachedConversation = applyAnthropicHistoryCacheBreakpoints(conversation, ANTHROPIC_MAX_CACHE_BREAKPOINTS - cacheMarkersUsed);
|
|
1377
1416
|
const params = {
|
|
1378
1417
|
model: modelId,
|
|
1379
|
-
messages:
|
|
1418
|
+
messages: cachedConversation,
|
|
1380
1419
|
max_tokens: resolveClaudeMaxTokens(modelId, options.maxTokens),
|
|
1381
1420
|
stream: true,
|
|
1382
1421
|
...(payload.system ? { system: payload.system } : {}),
|
|
@@ -1406,6 +1445,12 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1406
1445
|
if (event.type === "message_start") {
|
|
1407
1446
|
totalInput += event.message.usage.input_tokens ?? 0;
|
|
1408
1447
|
totalOutput += event.message.usage.output_tokens ?? 0;
|
|
1448
|
+
// Anthropic reports cache reads/writes SEPARATELY from
|
|
1449
|
+
// input_tokens on the same message_start event — without these
|
|
1450
|
+
// the streaming path silently drops all cache accounting.
|
|
1451
|
+
totalCacheRead += event.message.usage.cache_read_input_tokens ?? 0;
|
|
1452
|
+
totalCacheWrite +=
|
|
1453
|
+
event.message.usage.cache_creation_input_tokens ?? 0;
|
|
1409
1454
|
}
|
|
1410
1455
|
else if (event.type === "content_block_start") {
|
|
1411
1456
|
blockTypes.set(event.index, event.content_block.type);
|
|
@@ -1578,7 +1623,11 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
1578
1623
|
resolveUsage({
|
|
1579
1624
|
promptTokens: totalInput,
|
|
1580
1625
|
completionTokens: totalOutput,
|
|
1581
|
-
totalTokens: totalInput + totalOutput,
|
|
1626
|
+
totalTokens: totalInput + totalCacheRead + totalCacheWrite + totalOutput,
|
|
1627
|
+
...(totalCacheRead > 0 ? { cacheReadTokens: totalCacheRead } : {}),
|
|
1628
|
+
...(totalCacheWrite > 0
|
|
1629
|
+
? { cacheCreationTokens: totalCacheWrite }
|
|
1630
|
+
: {}),
|
|
1582
1631
|
});
|
|
1583
1632
|
resolveFinish(lastStop ?? "stop");
|
|
1584
1633
|
};
|
|
@@ -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: () => {
|
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
|
/**
|