@juspay/neurolink 10.6.4 → 10.6.5
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 +6 -0
- package/dist/analytics/pricing.d.ts +4 -1
- package/dist/analytics/pricing.js +6 -2
- package/dist/browser/neurolink.min.js +400 -400
- package/dist/core/analytics.js +8 -2
- package/dist/core/baseProvider.js +9 -7
- package/dist/core/modules/GenerationHandler.d.ts +8 -0
- package/dist/core/modules/GenerationHandler.js +28 -38
- package/dist/core/modules/TelemetryHandler.d.ts +3 -9
- package/dist/core/modules/TelemetryHandler.js +17 -12
- package/dist/lib/analytics/pricing.d.ts +4 -1
- package/dist/lib/analytics/pricing.js +6 -2
- package/dist/lib/core/analytics.js +8 -2
- package/dist/lib/core/baseProvider.js +9 -7
- package/dist/lib/core/modules/GenerationHandler.d.ts +8 -0
- package/dist/lib/core/modules/GenerationHandler.js +28 -38
- package/dist/lib/core/modules/TelemetryHandler.d.ts +3 -9
- package/dist/lib/core/modules/TelemetryHandler.js +17 -12
- package/dist/lib/neurolink.js +18 -1
- package/dist/lib/providers/amazonBedrock.js +68 -6
- package/dist/lib/providers/anthropic.js +50 -16
- package/dist/lib/providers/googleAiStudio.js +29 -4
- package/dist/lib/providers/googleNativeGemini3.js +10 -0
- package/dist/lib/providers/googleVertex.js +150 -25
- package/dist/lib/providers/litellm.js +13 -2
- package/dist/lib/providers/openAI.js +13 -2
- package/dist/lib/providers/openaiChatCompletionsBase.js +45 -10
- package/dist/lib/providers/openaiChatCompletionsClient.d.ts +2 -14
- package/dist/lib/providers/openaiChatCompletionsClient.js +20 -1
- package/dist/lib/providers/sagemaker/language-model.js +5 -3
- package/dist/lib/providers/sagemaker/parsers.js +18 -9
- package/dist/lib/providers/sagemaker/streaming.d.ts +9 -0
- package/dist/lib/providers/sagemaker/streaming.js +64 -6
- package/dist/lib/types/common.d.ts +3 -0
- package/dist/lib/types/openaiCompatible.d.ts +8 -7
- package/dist/lib/types/providers.d.ts +6 -0
- package/dist/lib/utils/pricing.js +15 -3
- package/dist/lib/utils/tokenUtils.js +38 -3
- package/dist/neurolink.js +18 -1
- package/dist/providers/amazonBedrock.js +68 -6
- package/dist/providers/anthropic.js +50 -16
- package/dist/providers/googleAiStudio.js +29 -4
- package/dist/providers/googleNativeGemini3.js +10 -0
- package/dist/providers/googleVertex.js +150 -25
- package/dist/providers/litellm.js +13 -2
- package/dist/providers/openAI.js +13 -2
- package/dist/providers/openaiChatCompletionsBase.js +45 -10
- package/dist/providers/openaiChatCompletionsClient.d.ts +2 -14
- package/dist/providers/openaiChatCompletionsClient.js +20 -1
- package/dist/providers/sagemaker/language-model.js +5 -3
- package/dist/providers/sagemaker/parsers.js +18 -9
- package/dist/providers/sagemaker/streaming.d.ts +9 -0
- package/dist/providers/sagemaker/streaming.js +64 -6
- package/dist/types/common.d.ts +3 -0
- package/dist/types/openaiCompatible.d.ts +8 -7
- package/dist/types/providers.d.ts +6 -0
- package/dist/utils/pricing.js +15 -3
- package/dist/utils/tokenUtils.js +38 -3
- package/package.json +2 -1
|
@@ -673,9 +673,28 @@ export const mergeUsage = (a, b) => {
|
|
|
673
673
|
if (!b) {
|
|
674
674
|
return a;
|
|
675
675
|
}
|
|
676
|
+
// Sum the nested details too — narrowing to the three flat scalars here
|
|
677
|
+
// silently dropped cached_tokens / reasoning_tokens the first time two
|
|
678
|
+
// steps were merged (single-step streams passed the object through
|
|
679
|
+
// untouched, so the loss only showed up in multi-step tool loops).
|
|
680
|
+
const cachedTokens = (a.prompt_tokens_details?.cached_tokens ?? 0) +
|
|
681
|
+
(b.prompt_tokens_details?.cached_tokens ?? 0);
|
|
682
|
+
const reasoningTokens = (a.completion_tokens_details?.reasoning_tokens ?? 0) +
|
|
683
|
+
(b.completion_tokens_details?.reasoning_tokens ?? 0);
|
|
684
|
+
// A step that omits total_tokens contributes its prompt+completion sum
|
|
685
|
+
// instead of 0 — otherwise one totals-reporting step next to one that
|
|
686
|
+
// omits it yields a nonzero-but-partial aggregate that downstream
|
|
687
|
+
// consumers trust over their own prompt+completion fallback.
|
|
688
|
+
const stepTotal = (u) => u.total_tokens || (u.prompt_tokens ?? 0) + (u.completion_tokens ?? 0);
|
|
676
689
|
return {
|
|
677
690
|
prompt_tokens: (a.prompt_tokens ?? 0) + (b.prompt_tokens ?? 0),
|
|
678
691
|
completion_tokens: (a.completion_tokens ?? 0) + (b.completion_tokens ?? 0),
|
|
679
|
-
total_tokens: (a
|
|
692
|
+
total_tokens: stepTotal(a) + stepTotal(b),
|
|
693
|
+
...(cachedTokens > 0
|
|
694
|
+
? { prompt_tokens_details: { cached_tokens: cachedTokens } }
|
|
695
|
+
: {}),
|
|
696
|
+
...(reasoningTokens > 0
|
|
697
|
+
? { completion_tokens_details: { reasoning_tokens: reasoningTokens } }
|
|
698
|
+
: {}),
|
|
680
699
|
};
|
|
681
700
|
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { randomUUID } from "crypto";
|
|
8
8
|
import { SageMakerRuntimeClient } from "./client.js";
|
|
9
9
|
import { handleSageMakerError } from "./errors.js";
|
|
10
|
-
import { estimateTokenUsage, createSageMakerStream } from "./streaming.js";
|
|
10
|
+
import { estimateTokenUsage, createSageMakerStream, parseUsageFromResponseBody, } from "./streaming.js";
|
|
11
11
|
import { createAdaptiveSemaphore } from "./adaptive-semaphore.js";
|
|
12
12
|
import { logger } from "../../utils/logger.js";
|
|
13
13
|
/**
|
|
@@ -140,8 +140,10 @@ export class SageMakerLanguageModel {
|
|
|
140
140
|
const generatedText = this.extractTextFromResponse(responseBody);
|
|
141
141
|
// Extract tool calls if present (Phase 4 enhancement)
|
|
142
142
|
const toolCalls = this.extractToolCallsFromResponse(responseBody);
|
|
143
|
-
//
|
|
144
|
-
|
|
143
|
+
// Prefer the endpoint's REAL token counts; only fall back to the
|
|
144
|
+
// char-heuristic estimate when the response reports none.
|
|
145
|
+
const usage = parseUsageFromResponseBody(responseBody) ??
|
|
146
|
+
estimateTokenUsage(promptText, generatedText);
|
|
145
147
|
// Determine finish reason based on response content
|
|
146
148
|
let finishReason = "stop";
|
|
147
149
|
if (toolCalls && toolCalls.length > 0) {
|
|
@@ -276,10 +276,13 @@ export class HuggingFaceStreamParser extends BaseStreamingParser {
|
|
|
276
276
|
return undefined;
|
|
277
277
|
}
|
|
278
278
|
const tokens = details.tokens;
|
|
279
|
+
const promptTokens = Number(tokens.input) || 0;
|
|
280
|
+
const completionTokens = Number(tokens.generated) || 0;
|
|
279
281
|
return {
|
|
280
|
-
promptTokens
|
|
281
|
-
completionTokens
|
|
282
|
-
|
|
282
|
+
promptTokens,
|
|
283
|
+
completionTokens,
|
|
284
|
+
// Endpoints that omit the total must not report 0 next to real counts.
|
|
285
|
+
total: Number(tokens.total) || promptTokens + completionTokens,
|
|
283
286
|
};
|
|
284
287
|
}
|
|
285
288
|
mapFinishReason(reason) {
|
|
@@ -447,10 +450,13 @@ export class LlamaStreamParser extends BaseStreamingParser {
|
|
|
447
450
|
return toolCall;
|
|
448
451
|
}
|
|
449
452
|
parseLlamaUsage(usage) {
|
|
453
|
+
const promptTokens = Number(usage.prompt_tokens) || 0;
|
|
454
|
+
const completionTokens = Number(usage.completion_tokens) || 0;
|
|
450
455
|
return {
|
|
451
|
-
promptTokens
|
|
452
|
-
completionTokens
|
|
453
|
-
|
|
456
|
+
promptTokens,
|
|
457
|
+
completionTokens,
|
|
458
|
+
// Endpoints that omit total_tokens must not report 0 next to real counts.
|
|
459
|
+
total: Number(usage.total_tokens) || promptTokens + completionTokens,
|
|
454
460
|
};
|
|
455
461
|
}
|
|
456
462
|
mapFinishReason(reason) {
|
|
@@ -569,10 +575,13 @@ export class CustomStreamParser extends BaseStreamingParser {
|
|
|
569
575
|
}
|
|
570
576
|
parseCustomUsage(data) {
|
|
571
577
|
const usage = (data.usage || data.tokens || {});
|
|
578
|
+
const promptTokens = Number(usage.prompt_tokens || usage.input_tokens) || 0;
|
|
579
|
+
const completionTokens = Number(usage.completion_tokens || usage.output_tokens) || 0;
|
|
572
580
|
return {
|
|
573
|
-
promptTokens
|
|
574
|
-
completionTokens
|
|
575
|
-
|
|
581
|
+
promptTokens,
|
|
582
|
+
completionTokens,
|
|
583
|
+
// Endpoints that omit total_tokens must not report 0 next to real counts.
|
|
584
|
+
total: Number(usage.total_tokens) || promptTokens + completionTokens,
|
|
576
585
|
};
|
|
577
586
|
}
|
|
578
587
|
}
|
|
@@ -29,6 +29,15 @@ export declare function createSyntheticStream(text: string, usage: SageMakerUsag
|
|
|
29
29
|
onChunk?: (chunk: SageMakerStreamChunk) => void;
|
|
30
30
|
onComplete?: (usage: SageMakerUsage) => void;
|
|
31
31
|
}): Promise<ReadableStream<unknown>>;
|
|
32
|
+
/**
|
|
33
|
+
* Extract REAL token usage from a complete (non-streaming) SageMaker
|
|
34
|
+
* response body when the endpoint reports it. Covers the OpenAI-compatible
|
|
35
|
+
* shape (vLLM / TGI-OpenAI: usage.prompt_tokens/completion_tokens), the
|
|
36
|
+
* generic input_tokens/output_tokens shape, and the HuggingFace TGI shape
|
|
37
|
+
* (details.tokens.input/generated). Returns undefined when no real counts
|
|
38
|
+
* are present so callers can fall back to estimateTokenUsage.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseUsageFromResponseBody(body: Record<string, unknown> | Array<Record<string, unknown>>): SageMakerUsage | undefined;
|
|
32
41
|
/**
|
|
33
42
|
* Estimate token usage from text content
|
|
34
43
|
*
|
|
@@ -87,12 +87,12 @@ async function createProtocolSpecificStream(responseStream, parser, capability,
|
|
|
87
87
|
if (done) {
|
|
88
88
|
// Stream ended - send final chunk if needed
|
|
89
89
|
if (!finalUsage && accumulatedText) {
|
|
90
|
-
finalUsage = estimateTokenUsage("", accumulatedText);
|
|
90
|
+
finalUsage = estimateTokenUsage(options.prompt ?? "", accumulatedText);
|
|
91
91
|
}
|
|
92
92
|
const finalChunk = {
|
|
93
93
|
type: "finish",
|
|
94
94
|
finishReason: "stop",
|
|
95
|
-
usage: finalUsage,
|
|
95
|
+
usage: toLanguageModelV2Usage(finalUsage),
|
|
96
96
|
};
|
|
97
97
|
controller.enqueue(finalChunk);
|
|
98
98
|
options.onComplete?.(finalUsage || {
|
|
@@ -182,11 +182,11 @@ async function createProtocolSpecificStream(responseStream, parser, capability,
|
|
|
182
182
|
if (parser.isComplete(chunk)) {
|
|
183
183
|
finalUsage =
|
|
184
184
|
parser.extractUsage(chunk) ||
|
|
185
|
-
estimateTokenUsage("", accumulatedText);
|
|
185
|
+
estimateTokenUsage(options.prompt ?? "", accumulatedText);
|
|
186
186
|
const finalChunk = {
|
|
187
187
|
type: "finish",
|
|
188
188
|
finishReason: chunk.finishReason || "stop",
|
|
189
|
-
usage: finalUsage,
|
|
189
|
+
usage: toLanguageModelV2Usage(finalUsage),
|
|
190
190
|
};
|
|
191
191
|
controller.enqueue(finalChunk);
|
|
192
192
|
options.onComplete?.(finalUsage);
|
|
@@ -271,7 +271,7 @@ async function createSyntheticStreamFromResponse(responseStream, options) {
|
|
|
271
271
|
const finalChunk = {
|
|
272
272
|
type: "finish",
|
|
273
273
|
finishReason: "stop",
|
|
274
|
-
usage,
|
|
274
|
+
usage: toLanguageModelV2Usage(usage),
|
|
275
275
|
};
|
|
276
276
|
controller.enqueue(finalChunk);
|
|
277
277
|
options.onComplete?.(usage);
|
|
@@ -305,7 +305,7 @@ export async function createSyntheticStream(text, usage, options = {}) {
|
|
|
305
305
|
const finalChunk = {
|
|
306
306
|
type: "finish",
|
|
307
307
|
finishReason: "stop",
|
|
308
|
-
usage,
|
|
308
|
+
usage: toLanguageModelV2Usage(usage),
|
|
309
309
|
};
|
|
310
310
|
controller.enqueue(finalChunk);
|
|
311
311
|
options.onComplete?.(usage);
|
|
@@ -313,6 +313,64 @@ export async function createSyntheticStream(text, usage, options = {}) {
|
|
|
313
313
|
},
|
|
314
314
|
});
|
|
315
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Map the internal SageMakerUsage shape to AI SDK v2 usage keys. Stream
|
|
318
|
+
* finish parts previously carried promptTokens/completionTokens, but the
|
|
319
|
+
* SDK's v2→v3 bridge reads inputTokens/outputTokens/totalTokens — every
|
|
320
|
+
* streamed SageMaker turn resolved to undefined (zero) usage.
|
|
321
|
+
*/
|
|
322
|
+
function toLanguageModelV2Usage(usage) {
|
|
323
|
+
const inputTokens = usage?.promptTokens ?? 0;
|
|
324
|
+
const outputTokens = usage?.completionTokens ?? 0;
|
|
325
|
+
return {
|
|
326
|
+
inputTokens,
|
|
327
|
+
outputTokens,
|
|
328
|
+
totalTokens: usage?.total || inputTokens + outputTokens,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Extract REAL token usage from a complete (non-streaming) SageMaker
|
|
333
|
+
* response body when the endpoint reports it. Covers the OpenAI-compatible
|
|
334
|
+
* shape (vLLM / TGI-OpenAI: usage.prompt_tokens/completion_tokens), the
|
|
335
|
+
* generic input_tokens/output_tokens shape, and the HuggingFace TGI shape
|
|
336
|
+
* (details.tokens.input/generated). Returns undefined when no real counts
|
|
337
|
+
* are present so callers can fall back to estimateTokenUsage.
|
|
338
|
+
*/
|
|
339
|
+
export function parseUsageFromResponseBody(body) {
|
|
340
|
+
// TGI-style endpoints wrap the response in an array
|
|
341
|
+
// ([{ generated_text, details: { tokens } }]) — normalize to the first
|
|
342
|
+
// item so those endpoints get real usage instead of the char estimate.
|
|
343
|
+
const responseBody = Array.isArray(body) ? body[0] : body;
|
|
344
|
+
if (!responseBody || typeof responseBody !== "object") {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
const usageRecord = (responseBody.usage ?? responseBody.tokens);
|
|
348
|
+
if (usageRecord && typeof usageRecord === "object") {
|
|
349
|
+
const promptTokens = Number(usageRecord.prompt_tokens ?? usageRecord.input_tokens) || 0;
|
|
350
|
+
const completionTokens = Number(usageRecord.completion_tokens ?? usageRecord.output_tokens) || 0;
|
|
351
|
+
if (promptTokens > 0 || completionTokens > 0) {
|
|
352
|
+
return {
|
|
353
|
+
promptTokens,
|
|
354
|
+
completionTokens,
|
|
355
|
+
total: Number(usageRecord.total_tokens) || promptTokens + completionTokens,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const details = responseBody.details;
|
|
360
|
+
const tokens = details?.tokens;
|
|
361
|
+
if (tokens && typeof tokens === "object") {
|
|
362
|
+
const promptTokens = Number(tokens.input) || 0;
|
|
363
|
+
const completionTokens = Number(tokens.generated) || 0;
|
|
364
|
+
if (promptTokens > 0 || completionTokens > 0) {
|
|
365
|
+
return {
|
|
366
|
+
promptTokens,
|
|
367
|
+
completionTokens,
|
|
368
|
+
total: Number(tokens.total) || promptTokens + completionTokens,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
316
374
|
/**
|
|
317
375
|
* Estimate token usage from text content
|
|
318
376
|
*
|
package/dist/types/common.d.ts
CHANGED
|
@@ -225,6 +225,7 @@ export type RawUsageObject = {
|
|
|
225
225
|
cacheReadTokens?: number;
|
|
226
226
|
cachedInputTokens?: number;
|
|
227
227
|
inputTokenDetails?: {
|
|
228
|
+
noCacheTokens?: number;
|
|
228
229
|
cacheReadTokens?: number;
|
|
229
230
|
cacheWriteTokens?: number;
|
|
230
231
|
};
|
|
@@ -248,6 +249,8 @@ export type DeferredUsage = {
|
|
|
248
249
|
totalTokens: number;
|
|
249
250
|
cacheReadTokens?: number;
|
|
250
251
|
cacheCreationTokens?: number;
|
|
252
|
+
/** Reasoning/thinking tokens — a SUBSET already included in completionTokens. */
|
|
253
|
+
reasoningTokens?: number;
|
|
251
254
|
};
|
|
252
255
|
/**
|
|
253
256
|
* Options for token extraction from raw usage objects.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NeuroLinkEvents, TypedEventEmitter } from "./common.js";
|
|
1
|
+
import type { DeferredUsage, NeuroLinkEvents, TypedEventEmitter } from "./common.js";
|
|
2
2
|
import type { JSONSchema7 } from "./middleware.js";
|
|
3
3
|
import type { LanguageModelV3, LanguageModelV3CallOptions } from "./middleware.js";
|
|
4
4
|
import type { StreamOptions } from "./stream.js";
|
|
@@ -271,12 +271,13 @@ export type OpenAICompatBuildBodyArgs = {
|
|
|
271
271
|
* the deferred analytics promises.
|
|
272
272
|
*/
|
|
273
273
|
export type OpenAICompatStreamLifecycleListeners = {
|
|
274
|
-
/**
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Fired once the deferred usage promise resolves with the final aggregated
|
|
276
|
+
* token counts. promptTokens is the UNCACHED remainder; cacheReadTokens
|
|
277
|
+
* carries the cached portion (non-overlapping convention), and
|
|
278
|
+
* reasoningTokens is a subset of completionTokens.
|
|
279
|
+
*/
|
|
280
|
+
onUsage?: (usage: DeferredUsage) => void;
|
|
280
281
|
/**
|
|
281
282
|
* Fired once the deferred finish promise resolves. `reason` is "stop",
|
|
282
283
|
* "length", "tool-calls", "content-filter", or "error". When the loop
|
|
@@ -1623,6 +1623,12 @@ export type CollectedChunkResult = {
|
|
|
1623
1623
|
cacheReadTokens?: number;
|
|
1624
1624
|
/** Cache creation tokens (symmetry; Gemini does not emit this). */
|
|
1625
1625
|
cacheCreationTokens?: number;
|
|
1626
|
+
/**
|
|
1627
|
+
* Gemini thinking tokens (usageMetadata.thoughtsTokenCount). Billed at the
|
|
1628
|
+
* output rate but NOT included in candidatesTokenCount — Gemini reports
|
|
1629
|
+
* totalTokenCount = prompt + candidates + thoughts.
|
|
1630
|
+
*/
|
|
1631
|
+
reasoningTokens?: number;
|
|
1626
1632
|
};
|
|
1627
1633
|
/** Push-based text channel for incremental streaming. */
|
|
1628
1634
|
export type TextChannel = {
|
package/dist/utils/pricing.js
CHANGED
|
@@ -653,15 +653,27 @@ function findRates(provider, model) {
|
|
|
653
653
|
if (!providerPricing) {
|
|
654
654
|
return undefined;
|
|
655
655
|
}
|
|
656
|
+
// Bedrock model IDs carry region/vendor prefixes ("us.anthropic.claude-…",
|
|
657
|
+
// "global.anthropic.claude-…", "anthropic.claude-…") or arrive as full
|
|
658
|
+
// inference-profile ARNs ("arn:aws:bedrock:…:inference-profile/us.anthropic.
|
|
659
|
+
// claude-…" — the form this repo's own setup guides recommend). None of
|
|
660
|
+
// those match the bare pricing keys ("claude-…"), so every Bedrock call
|
|
661
|
+
// priced to $0. Strip everything up to and including the vendor prefix; the
|
|
662
|
+
// trailing "-v1:0" version suffix is absorbed by the longest-prefix match
|
|
663
|
+
// below. Non-Anthropic Bedrock models (meta./amazon./mistral.) don't match
|
|
664
|
+
// and fall through unchanged, exactly as before.
|
|
665
|
+
const modelKey = stripped === "bedrock" || stripped === "amazonbedrock"
|
|
666
|
+
? model.replace(/^.*\banthropic\./, "")
|
|
667
|
+
: model;
|
|
656
668
|
// Exact match
|
|
657
|
-
if (providerPricing[
|
|
658
|
-
return providerPricing[
|
|
669
|
+
if (providerPricing[modelKey]) {
|
|
670
|
+
return providerPricing[modelKey];
|
|
659
671
|
}
|
|
660
672
|
// Longest-prefix match (skip the synthetic "_default" sentinel below)
|
|
661
673
|
const sortedKeys = Object.keys(providerPricing)
|
|
662
674
|
.filter((k) => k !== "_default")
|
|
663
675
|
.sort((a, b) => b.length - a.length);
|
|
664
|
-
const key = sortedKeys.find((k) =>
|
|
676
|
+
const key = sortedKeys.find((k) => modelKey.startsWith(k));
|
|
665
677
|
if (key) {
|
|
666
678
|
return providerPricing[key];
|
|
667
679
|
}
|
package/dist/utils/tokenUtils.js
CHANGED
|
@@ -44,10 +44,13 @@ export function extractOutputTokens(usage) {
|
|
|
44
44
|
* Falls back to input + output if total is not provided
|
|
45
45
|
*/
|
|
46
46
|
export function extractTotalTokens(usage, input, output) {
|
|
47
|
-
|
|
47
|
+
// A literal 0 alongside non-zero components means the provider omitted the
|
|
48
|
+
// figure (several gateways/parsers emit total: 0) — fall through to the
|
|
49
|
+
// computed sum instead of letting the zero win.
|
|
50
|
+
if (typeof usage.total === "number" && usage.total > 0) {
|
|
48
51
|
return usage.total;
|
|
49
52
|
}
|
|
50
|
-
if (typeof usage.totalTokens === "number") {
|
|
53
|
+
if (typeof usage.totalTokens === "number" && usage.totalTokens > 0) {
|
|
51
54
|
return usage.totalTokens;
|
|
52
55
|
}
|
|
53
56
|
return input + output;
|
|
@@ -185,7 +188,6 @@ export function extractTokenUsage(result, options = {}) {
|
|
|
185
188
|
// Extract base token counts
|
|
186
189
|
let input = extractInputTokens(usage);
|
|
187
190
|
const output = extractOutputTokens(usage);
|
|
188
|
-
const total = extractTotalTokens(usage, input, output);
|
|
189
191
|
// Extract optional token fields
|
|
190
192
|
const reasoning = extractReasoningTokens(usage);
|
|
191
193
|
const cacheCreationTokens = extractCacheCreationTokens(usage);
|
|
@@ -204,6 +206,39 @@ export function extractTokenUsage(result, options = {}) {
|
|
|
204
206
|
input = Math.max(0, input - overlappingCached);
|
|
205
207
|
}
|
|
206
208
|
}
|
|
209
|
+
// ai@6-shape rebase: when the cache values came ONLY from the ai@6
|
|
210
|
+
// normalized shape (cachedInputTokens / inputTokenDetails.*), the flat
|
|
211
|
+
// input is cache-INCLUSIVE — asLanguageModelUsage reports
|
|
212
|
+
// inputTokens.total = noCache + cacheRead + cacheWrite — so `input` must
|
|
213
|
+
// be rebased onto the uncached portion or calculateCost bills the cached
|
|
214
|
+
// tokens twice (full input rate + cacheRead/cacheCreation rate). The
|
|
215
|
+
// native fields excluded below (cacheReadInputTokens / cacheReadTokens /
|
|
216
|
+
// cacheCreationInputTokens / cacheCreationTokens) follow the
|
|
217
|
+
// non-overlapping convention where input is already the uncached
|
|
218
|
+
// remainder, and the overlapping snake_case shape was already rebased
|
|
219
|
+
// above — neither must be rebased again.
|
|
220
|
+
if ((cacheReadTokens !== undefined || cacheCreationTokens !== undefined) &&
|
|
221
|
+
usage.cacheReadInputTokens === undefined &&
|
|
222
|
+
usage.cacheReadTokens === undefined &&
|
|
223
|
+
usage.cacheCreationInputTokens === undefined &&
|
|
224
|
+
usage.cacheCreationTokens === undefined &&
|
|
225
|
+
usage.prompt_tokens_details?.cached_tokens === undefined &&
|
|
226
|
+
(usage.cachedInputTokens !== undefined ||
|
|
227
|
+
usage.inputTokenDetails !== undefined)) {
|
|
228
|
+
const noCacheTokens = usage.inputTokenDetails?.noCacheTokens;
|
|
229
|
+
if (typeof noCacheTokens === "number") {
|
|
230
|
+
input = noCacheTokens;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
input = Math.max(0, input -
|
|
234
|
+
(cacheReadTokens ?? 0) -
|
|
235
|
+
(usage.inputTokenDetails?.cacheWriteTokens ?? 0));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Total: prefer the provider-reported figure; the fallback conserves every
|
|
239
|
+
// billed component — cache tokens are additive on top of the (post-rebase)
|
|
240
|
+
// uncached input. Reasoning is already inside `output`, so it is NOT added.
|
|
241
|
+
const total = extractTotalTokens(usage, input + (cacheReadTokens ?? 0) + (cacheCreationTokens ?? 0), output);
|
|
207
242
|
// Calculate cache savings if enabled
|
|
208
243
|
const cacheSavingsPercent = calculateCacheSavings
|
|
209
244
|
? calculateCacheSavingsPercent(cacheReadTokens, input)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "10.6.
|
|
3
|
+
"version": "10.6.5",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|
|
@@ -104,6 +104,7 @@
|
|
|
104
104
|
"test:google-native": "npx tsx test/continuous-test-suite-google-native.ts",
|
|
105
105
|
"test:gemini-abort": "npx tsx test/continuous-test-suite-gemini-abort.ts",
|
|
106
106
|
"test:anthropic-cap": "npx tsx test/continuous-test-suite-anthropic-cap.ts",
|
|
107
|
+
"test:token-usage": "npx tsx test/continuous-test-suite-token-usage.ts",
|
|
107
108
|
"test:tts": "npx tsx test/continuous-test-suite-tts.ts",
|
|
108
109
|
"test:voice": "npx tsx test/continuous-test-suite-voice.ts",
|
|
109
110
|
"test:voice-server": "npx tsx test/continuous-test-suite-voice-server.ts",
|