@juspay/neurolink 10.6.3 → 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 +12 -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/processors/media/index.d.ts +1 -1
- package/dist/lib/processors/media/index.js +1 -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/processors/media/index.d.ts +1 -1
- package/dist/processors/media/index.js +1 -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
|
@@ -591,6 +591,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
591
591
|
let totalInputTokens = 0;
|
|
592
592
|
let totalOutputTokens = 0;
|
|
593
593
|
let totalCacheReadTokens = 0;
|
|
594
|
+
let totalReasoningTokens = 0;
|
|
594
595
|
let step = 0;
|
|
595
596
|
let completedWithFinalAnswer = false;
|
|
596
597
|
const failedTools = new Map();
|
|
@@ -627,6 +628,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
627
628
|
totalInputTokens += chunkResult.inputTokens;
|
|
628
629
|
totalOutputTokens += chunkResult.outputTokens;
|
|
629
630
|
totalCacheReadTokens += chunkResult.cacheReadTokens ?? 0;
|
|
631
|
+
totalReasoningTokens += chunkResult.reasoningTokens ?? 0;
|
|
630
632
|
const stepText = extractTextFromParts(chunkResult.rawResponseParts);
|
|
631
633
|
// If no function calls, this was the final step — channel
|
|
632
634
|
// already received all text parts incrementally.
|
|
@@ -723,13 +725,21 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
723
725
|
model: modelName,
|
|
724
726
|
tokenUsage: {
|
|
725
727
|
input: adjustedInputTokens,
|
|
726
|
-
output
|
|
728
|
+
// Thinking tokens are billed at the output rate but Gemini
|
|
729
|
+
// does NOT include them in candidatesTokenCount, so they
|
|
730
|
+
// are folded into `output` — what calculateCost bills at
|
|
731
|
+
// the output rate — with `reasoning` as the subset.
|
|
732
|
+
output: totalOutputTokens + totalReasoningTokens,
|
|
727
733
|
total: adjustedInputTokens +
|
|
728
734
|
totalCacheReadTokens +
|
|
729
|
-
totalOutputTokens
|
|
735
|
+
totalOutputTokens +
|
|
736
|
+
totalReasoningTokens,
|
|
730
737
|
...(totalCacheReadTokens > 0
|
|
731
738
|
? { cacheReadTokens: totalCacheReadTokens }
|
|
732
739
|
: {}),
|
|
740
|
+
...(totalReasoningTokens > 0
|
|
741
|
+
? { reasoning: totalReasoningTokens }
|
|
742
|
+
: {}),
|
|
733
743
|
},
|
|
734
744
|
requestDuration: responseTime,
|
|
735
745
|
timestamp: new Date().toISOString(),
|
|
@@ -862,6 +872,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
862
872
|
let totalInputTokens = 0;
|
|
863
873
|
let totalOutputTokens = 0;
|
|
864
874
|
let totalCacheReadTokens = 0;
|
|
875
|
+
let totalReasoningTokens = 0;
|
|
865
876
|
const allToolCalls = [];
|
|
866
877
|
const toolExecutions = [];
|
|
867
878
|
let step = 0;
|
|
@@ -892,6 +903,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
892
903
|
totalInputTokens += chunkResult.inputTokens;
|
|
893
904
|
totalOutputTokens += chunkResult.outputTokens;
|
|
894
905
|
totalCacheReadTokens += chunkResult.cacheReadTokens ?? 0;
|
|
906
|
+
totalReasoningTokens += chunkResult.reasoningTokens ?? 0;
|
|
895
907
|
const stepText = extractTextFromParts(chunkResult.rawResponseParts);
|
|
896
908
|
// If no function calls, we're done
|
|
897
909
|
if (chunkResult.stepFunctionCalls.length === 0) {
|
|
@@ -975,12 +987,25 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
975
987
|
model: modelName,
|
|
976
988
|
usage: {
|
|
977
989
|
input: adjustedInputTokens,
|
|
978
|
-
output
|
|
979
|
-
|
|
990
|
+
// Thinking tokens are billed at the output rate but Gemini
|
|
991
|
+
// does NOT include them in candidatesTokenCount, so they are
|
|
992
|
+
// folded into `output` — what calculateCost bills at the
|
|
993
|
+
// output rate — with `reasoning` as the subset.
|
|
994
|
+
output: totalOutputTokens + totalReasoningTokens,
|
|
995
|
+
total: adjustedInputTokens +
|
|
996
|
+
totalCacheReadTokens +
|
|
997
|
+
totalOutputTokens +
|
|
998
|
+
totalReasoningTokens,
|
|
980
999
|
...(totalCacheReadTokens > 0
|
|
981
1000
|
? { cacheReadTokens: totalCacheReadTokens }
|
|
982
1001
|
: {}),
|
|
1002
|
+
...(totalReasoningTokens > 0
|
|
1003
|
+
? { reasoning: totalReasoningTokens }
|
|
1004
|
+
: {}),
|
|
983
1005
|
},
|
|
1006
|
+
...(totalReasoningTokens > 0 && {
|
|
1007
|
+
reasoningTokens: totalReasoningTokens,
|
|
1008
|
+
}),
|
|
984
1009
|
responseTime,
|
|
985
1010
|
toolsUsed: allToolCalls.map((tc) => tc.toolName),
|
|
986
1011
|
toolExecutions: resolveToolExecutionRecords(options, toolExecutions),
|
|
@@ -601,6 +601,7 @@ export async function collectStreamChunks(stream) {
|
|
|
601
601
|
let inputTokens = 0;
|
|
602
602
|
let outputTokens = 0;
|
|
603
603
|
let cacheReadTokens = 0;
|
|
604
|
+
let reasoningTokens = 0;
|
|
604
605
|
for await (const chunk of stream) {
|
|
605
606
|
// Extract raw parts from candidates FIRST
|
|
606
607
|
// This avoids using chunk.text which triggers SDK warning when
|
|
@@ -623,6 +624,9 @@ export async function collectStreamChunks(stream) {
|
|
|
623
624
|
// cachedContentTokenCount is OVERLAPPING (a subset already inside
|
|
624
625
|
// promptTokenCount). Surface it so the call site subtracts once.
|
|
625
626
|
cacheReadTokens = Math.max(cacheReadTokens, usage.cachedContentTokenCount || 0);
|
|
627
|
+
// thoughtsTokenCount (thinking tokens, billed at the output rate) is
|
|
628
|
+
// NOT part of candidatesTokenCount — surface it separately.
|
|
629
|
+
reasoningTokens = Math.max(reasoningTokens, usage.thoughtsTokenCount || 0);
|
|
626
630
|
}
|
|
627
631
|
}
|
|
628
632
|
return {
|
|
@@ -631,6 +635,7 @@ export async function collectStreamChunks(stream) {
|
|
|
631
635
|
inputTokens,
|
|
632
636
|
outputTokens,
|
|
633
637
|
cacheReadTokens,
|
|
638
|
+
reasoningTokens,
|
|
634
639
|
};
|
|
635
640
|
}
|
|
636
641
|
/**
|
|
@@ -724,6 +729,7 @@ export async function collectStreamChunksIncremental(stream, channel) {
|
|
|
724
729
|
let inputTokens = 0;
|
|
725
730
|
let outputTokens = 0;
|
|
726
731
|
let cacheReadTokens = 0;
|
|
732
|
+
let reasoningTokens = 0;
|
|
727
733
|
for await (const chunk of stream) {
|
|
728
734
|
const chunkRecord = chunk;
|
|
729
735
|
const candidates = chunkRecord.candidates;
|
|
@@ -748,6 +754,9 @@ export async function collectStreamChunksIncremental(stream, channel) {
|
|
|
748
754
|
// cachedContentTokenCount is OVERLAPPING (a subset already inside
|
|
749
755
|
// promptTokenCount). Surface it so the call site subtracts once.
|
|
750
756
|
cacheReadTokens = Math.max(cacheReadTokens, usage.cachedContentTokenCount || 0);
|
|
757
|
+
// thoughtsTokenCount (thinking tokens, billed at the output rate) is
|
|
758
|
+
// NOT part of candidatesTokenCount — surface it separately.
|
|
759
|
+
reasoningTokens = Math.max(reasoningTokens, usage.thoughtsTokenCount || 0);
|
|
751
760
|
}
|
|
752
761
|
}
|
|
753
762
|
return {
|
|
@@ -756,6 +765,7 @@ export async function collectStreamChunksIncremental(stream, channel) {
|
|
|
756
765
|
inputTokens,
|
|
757
766
|
outputTokens,
|
|
758
767
|
cacheReadTokens,
|
|
768
|
+
reasoningTokens,
|
|
759
769
|
};
|
|
760
770
|
}
|
|
761
771
|
/**
|
|
@@ -1469,11 +1469,14 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1469
1469
|
// into a provider "prompt too long" rejection mid-loop.
|
|
1470
1470
|
const contextGuard = createContextGuard(getContextWindowSize("vertex", modelName));
|
|
1471
1471
|
let hitContextLimit = false;
|
|
1472
|
-
// Track token usage across all steps
|
|
1473
|
-
//
|
|
1472
|
+
// Track token usage across all steps. Every loop step is a separate
|
|
1473
|
+
// billed API call, so per-step usage is folded into these turn totals
|
|
1474
|
+
// after each step's chunk drain — assigning them directly from chunk
|
|
1475
|
+
// metadata would record only the last step of a multi-step tool loop.
|
|
1474
1476
|
let totalInputTokens = 0;
|
|
1475
1477
|
let totalOutputTokens = 0;
|
|
1476
1478
|
let totalCacheReadTokens = 0;
|
|
1479
|
+
let totalReasoningTokens = 0;
|
|
1477
1480
|
// Track text parts as they arrive from the SDK so the returned async
|
|
1478
1481
|
// iterable yields multiple chunks instead of a single buffered chunk.
|
|
1479
1482
|
// The CLI's chunk-count smoke test asserts > 1 stream chunks for any
|
|
@@ -1557,6 +1560,18 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1557
1560
|
// lastFinishReason) — drives the single MALFORMED_FUNCTION_CALL
|
|
1558
1561
|
// retry below.
|
|
1559
1562
|
let stepFinishReason;
|
|
1563
|
+
// Per-step usage trackers for WRITE-THROUGH accumulation: within a
|
|
1564
|
+
// step's chunk stream the counts are latest-wins (promptTokenCount
|
|
1565
|
+
// arrives once in the final chunk; candidates/thoughts counts are
|
|
1566
|
+
// cumulative across chunks), so each chunk folds only the DELTA
|
|
1567
|
+
// over this step's previous value into the turn totals. The totals
|
|
1568
|
+
// are therefore correct at every point mid-drain — a step killed
|
|
1569
|
+
// mid-stream (abort / turn deadline / stall watchdog) still counts
|
|
1570
|
+
// the billed tokens it already reported.
|
|
1571
|
+
let stepInputTokens = 0;
|
|
1572
|
+
let stepOutputTokens = 0;
|
|
1573
|
+
let stepCacheReadTokens = 0;
|
|
1574
|
+
let stepReasoningTokens = 0;
|
|
1560
1575
|
for await (const chunk of stream) {
|
|
1561
1576
|
turnClock.noteProgress();
|
|
1562
1577
|
// Extract raw parts from candidates FIRST
|
|
@@ -1591,18 +1606,34 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1591
1606
|
// Take the latest promptTokenCount (usually only in final chunk)
|
|
1592
1607
|
if (usageMetadata.promptTokenCount !== undefined &&
|
|
1593
1608
|
usageMetadata.promptTokenCount > 0) {
|
|
1594
|
-
totalInputTokens
|
|
1609
|
+
totalInputTokens +=
|
|
1610
|
+
usageMetadata.promptTokenCount - stepInputTokens;
|
|
1611
|
+
stepInputTokens = usageMetadata.promptTokenCount;
|
|
1595
1612
|
// Feed the context guard the REAL prompt size of this call.
|
|
1596
1613
|
contextGuard.noteUsage(usageMetadata.promptTokenCount, usageMetadata.candidatesTokenCount ?? 0);
|
|
1597
1614
|
// cachedContentTokenCount is OVERLAPPING (a subset already inside
|
|
1598
|
-
// promptTokenCount). Clamp to the prompt count so
|
|
1599
|
-
// step
|
|
1600
|
-
|
|
1615
|
+
// promptTokenCount). Clamp to the prompt count so an uncached
|
|
1616
|
+
// step reports 0 instead of a stale cached value.
|
|
1617
|
+
const chunkCacheReadTokens = Math.min(usageMetadata.cachedContentTokenCount ?? 0, usageMetadata.promptTokenCount);
|
|
1618
|
+
totalCacheReadTokens +=
|
|
1619
|
+
chunkCacheReadTokens - stepCacheReadTokens;
|
|
1620
|
+
stepCacheReadTokens = chunkCacheReadTokens;
|
|
1601
1621
|
}
|
|
1602
1622
|
// Take the latest candidatesTokenCount (accumulates through chunks)
|
|
1603
1623
|
if (usageMetadata.candidatesTokenCount !== undefined &&
|
|
1604
1624
|
usageMetadata.candidatesTokenCount > 0) {
|
|
1605
|
-
totalOutputTokens
|
|
1625
|
+
totalOutputTokens +=
|
|
1626
|
+
usageMetadata.candidatesTokenCount - stepOutputTokens;
|
|
1627
|
+
stepOutputTokens = usageMetadata.candidatesTokenCount;
|
|
1628
|
+
}
|
|
1629
|
+
// thoughtsTokenCount (thinking tokens, billed at the output
|
|
1630
|
+
// rate) is NOT part of candidatesTokenCount — Gemini reports
|
|
1631
|
+
// totalTokenCount = prompt + candidates + thoughts.
|
|
1632
|
+
if (usageMetadata.thoughtsTokenCount !== undefined &&
|
|
1633
|
+
usageMetadata.thoughtsTokenCount > 0) {
|
|
1634
|
+
totalReasoningTokens +=
|
|
1635
|
+
usageMetadata.thoughtsTokenCount - stepReasoningTokens;
|
|
1636
|
+
stepReasoningTokens = usageMetadata.thoughtsTokenCount;
|
|
1606
1637
|
}
|
|
1607
1638
|
}
|
|
1608
1639
|
}
|
|
@@ -1991,6 +2022,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1991
2022
|
incrementalTextChunks.push(synth.text);
|
|
1992
2023
|
totalInputTokens += synth.inputTokens;
|
|
1993
2024
|
totalOutputTokens += synth.outputTokens;
|
|
2025
|
+
totalReasoningTokens += synth.reasoningTokens ?? 0;
|
|
1994
2026
|
if (synth.finishReason) {
|
|
1995
2027
|
lastFinishReason = synth.finishReason;
|
|
1996
2028
|
}
|
|
@@ -2069,11 +2101,22 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2069
2101
|
rawFinishReason: lastFinishReason,
|
|
2070
2102
|
usage: {
|
|
2071
2103
|
input: adjustedInputTokens,
|
|
2072
|
-
output
|
|
2073
|
-
|
|
2104
|
+
// Thinking tokens are billed at the output rate but Gemini does NOT
|
|
2105
|
+
// include them in candidatesTokenCount (totalTokenCount = prompt +
|
|
2106
|
+
// candidates + thoughts), so they are folded into `output` — that is
|
|
2107
|
+
// what calculateCost bills at the output rate — with `reasoning`
|
|
2108
|
+
// reporting the thinking subset.
|
|
2109
|
+
output: totalOutputTokens + totalReasoningTokens,
|
|
2110
|
+
total: adjustedInputTokens +
|
|
2111
|
+
totalCacheReadTokens +
|
|
2112
|
+
totalOutputTokens +
|
|
2113
|
+
totalReasoningTokens,
|
|
2074
2114
|
...(totalCacheReadTokens > 0 && {
|
|
2075
2115
|
cacheReadTokens: totalCacheReadTokens,
|
|
2076
2116
|
}),
|
|
2117
|
+
...(totalReasoningTokens > 0 && {
|
|
2118
|
+
reasoning: totalReasoningTokens,
|
|
2119
|
+
}),
|
|
2077
2120
|
},
|
|
2078
2121
|
toolCalls: externalToolCalls.map((tc) => ({
|
|
2079
2122
|
toolName: tc.toolName,
|
|
@@ -2409,11 +2452,14 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2409
2452
|
// into a provider "prompt too long" rejection mid-loop.
|
|
2410
2453
|
const contextGuard = createContextGuard(getContextWindowSize("vertex", modelName));
|
|
2411
2454
|
let hitContextLimit = false;
|
|
2412
|
-
// Track token usage across all steps
|
|
2413
|
-
//
|
|
2455
|
+
// Track token usage across all steps. Every loop step is a separate
|
|
2456
|
+
// billed API call, so per-step usage is folded into these turn totals
|
|
2457
|
+
// after each step's chunk drain — assigning them directly from chunk
|
|
2458
|
+
// metadata would record only the last step of a multi-step tool loop.
|
|
2414
2459
|
let totalInputTokens = 0;
|
|
2415
2460
|
let totalOutputTokens = 0;
|
|
2416
2461
|
let totalCacheReadTokens = 0;
|
|
2462
|
+
let totalReasoningTokens = 0;
|
|
2417
2463
|
// Abort scaffolding (mirrors executeNativeAnthropicStream). The native
|
|
2418
2464
|
// Gemini SDK cancels via config.abortSignal, so drive an internal
|
|
2419
2465
|
// AbortController: the caller's signal and the turn clock's watchdogs
|
|
@@ -2488,6 +2534,18 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2488
2534
|
// lastFinishReason) — drives the single MALFORMED_FUNCTION_CALL
|
|
2489
2535
|
// retry below.
|
|
2490
2536
|
let stepFinishReason;
|
|
2537
|
+
// Per-step usage trackers for WRITE-THROUGH accumulation: within a
|
|
2538
|
+
// step's chunk stream the counts are latest-wins (promptTokenCount
|
|
2539
|
+
// arrives once in the final chunk; candidates/thoughts counts are
|
|
2540
|
+
// cumulative across chunks), so each chunk folds only the DELTA
|
|
2541
|
+
// over this step's previous value into the turn totals. The totals
|
|
2542
|
+
// are therefore correct at every point mid-drain — a step killed
|
|
2543
|
+
// mid-stream (abort / turn deadline / stall watchdog) still counts
|
|
2544
|
+
// the billed tokens it already reported.
|
|
2545
|
+
let stepInputTokens = 0;
|
|
2546
|
+
let stepOutputTokens = 0;
|
|
2547
|
+
let stepCacheReadTokens = 0;
|
|
2548
|
+
let stepReasoningTokens = 0;
|
|
2491
2549
|
// Collect all chunks from stream
|
|
2492
2550
|
for await (const chunk of stream) {
|
|
2493
2551
|
turnClock.noteProgress();
|
|
@@ -2518,18 +2576,34 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2518
2576
|
// Take the latest promptTokenCount (usually only in final chunk)
|
|
2519
2577
|
if (usageMetadata.promptTokenCount !== undefined &&
|
|
2520
2578
|
usageMetadata.promptTokenCount > 0) {
|
|
2521
|
-
totalInputTokens
|
|
2579
|
+
totalInputTokens +=
|
|
2580
|
+
usageMetadata.promptTokenCount - stepInputTokens;
|
|
2581
|
+
stepInputTokens = usageMetadata.promptTokenCount;
|
|
2522
2582
|
// Feed the context guard the REAL prompt size of this call.
|
|
2523
2583
|
contextGuard.noteUsage(usageMetadata.promptTokenCount, usageMetadata.candidatesTokenCount ?? 0);
|
|
2524
2584
|
// cachedContentTokenCount is OVERLAPPING (a subset already inside
|
|
2525
|
-
// promptTokenCount). Clamp to the prompt count so
|
|
2526
|
-
// step
|
|
2527
|
-
|
|
2585
|
+
// promptTokenCount). Clamp to the prompt count so an uncached
|
|
2586
|
+
// step reports 0 instead of a stale cached value.
|
|
2587
|
+
const chunkCacheReadTokens = Math.min(usageMetadata.cachedContentTokenCount ?? 0, usageMetadata.promptTokenCount);
|
|
2588
|
+
totalCacheReadTokens +=
|
|
2589
|
+
chunkCacheReadTokens - stepCacheReadTokens;
|
|
2590
|
+
stepCacheReadTokens = chunkCacheReadTokens;
|
|
2528
2591
|
}
|
|
2529
2592
|
// Take the latest candidatesTokenCount (accumulates through chunks)
|
|
2530
2593
|
if (usageMetadata.candidatesTokenCount !== undefined &&
|
|
2531
2594
|
usageMetadata.candidatesTokenCount > 0) {
|
|
2532
|
-
totalOutputTokens
|
|
2595
|
+
totalOutputTokens +=
|
|
2596
|
+
usageMetadata.candidatesTokenCount - stepOutputTokens;
|
|
2597
|
+
stepOutputTokens = usageMetadata.candidatesTokenCount;
|
|
2598
|
+
}
|
|
2599
|
+
// thoughtsTokenCount (thinking tokens, billed at the output
|
|
2600
|
+
// rate) is NOT part of candidatesTokenCount — Gemini reports
|
|
2601
|
+
// totalTokenCount = prompt + candidates + thoughts.
|
|
2602
|
+
if (usageMetadata.thoughtsTokenCount !== undefined &&
|
|
2603
|
+
usageMetadata.thoughtsTokenCount > 0) {
|
|
2604
|
+
totalReasoningTokens +=
|
|
2605
|
+
usageMetadata.thoughtsTokenCount - stepReasoningTokens;
|
|
2606
|
+
stepReasoningTokens = usageMetadata.thoughtsTokenCount;
|
|
2533
2607
|
}
|
|
2534
2608
|
}
|
|
2535
2609
|
}
|
|
@@ -2912,6 +2986,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2912
2986
|
finalText = synth.text;
|
|
2913
2987
|
totalInputTokens += synth.inputTokens;
|
|
2914
2988
|
totalOutputTokens += synth.outputTokens;
|
|
2989
|
+
totalReasoningTokens += synth.reasoningTokens ?? 0;
|
|
2915
2990
|
if (synth.finishReason) {
|
|
2916
2991
|
lastFinishReason = synth.finishReason;
|
|
2917
2992
|
}
|
|
@@ -2974,12 +3049,26 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2974
3049
|
stepsUsed: step,
|
|
2975
3050
|
usage: {
|
|
2976
3051
|
input: adjustedInputTokens,
|
|
2977
|
-
output
|
|
2978
|
-
|
|
3052
|
+
// Thinking tokens are billed at the output rate but Gemini does NOT
|
|
3053
|
+
// include them in candidatesTokenCount (totalTokenCount = prompt +
|
|
3054
|
+
// candidates + thoughts), so they are folded into `output` — that is
|
|
3055
|
+
// what calculateCost bills at the output rate — with `reasoning`
|
|
3056
|
+
// reporting the thinking subset.
|
|
3057
|
+
output: totalOutputTokens + totalReasoningTokens,
|
|
3058
|
+
total: adjustedInputTokens +
|
|
3059
|
+
totalCacheReadTokens +
|
|
3060
|
+
totalOutputTokens +
|
|
3061
|
+
totalReasoningTokens,
|
|
2979
3062
|
...(totalCacheReadTokens > 0 && {
|
|
2980
3063
|
cacheReadTokens: totalCacheReadTokens,
|
|
2981
3064
|
}),
|
|
3065
|
+
...(totalReasoningTokens > 0 && {
|
|
3066
|
+
reasoning: totalReasoningTokens,
|
|
3067
|
+
}),
|
|
2982
3068
|
},
|
|
3069
|
+
...(totalReasoningTokens > 0 && {
|
|
3070
|
+
reasoningTokens: totalReasoningTokens,
|
|
3071
|
+
}),
|
|
2983
3072
|
responseTime,
|
|
2984
3073
|
toolsUsed: externalToolCalls.map((tc) => tc.toolName),
|
|
2985
3074
|
toolExecutions: resolveToolExecutionRecords(options, externalToolExecutions),
|
|
@@ -3016,7 +3105,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3016
3105
|
// Already aborted — never issue the synth request (would add +300s after a
|
|
3017
3106
|
// blown budget). Return empty so the caller maps to the graceful message.
|
|
3018
3107
|
if (abortSignal?.aborted) {
|
|
3019
|
-
return { text: "", inputTokens: 0, outputTokens: 0 };
|
|
3108
|
+
return { text: "", inputTokens: 0, outputTokens: 0, reasoningTokens: 0 };
|
|
3020
3109
|
}
|
|
3021
3110
|
try {
|
|
3022
3111
|
// Shallow clone so the loop's config is never mutated; dropping the
|
|
@@ -3053,6 +3142,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3053
3142
|
let finishReason;
|
|
3054
3143
|
let inputTokens = 0;
|
|
3055
3144
|
let outputTokens = 0;
|
|
3145
|
+
let reasoningTokens = 0;
|
|
3056
3146
|
for await (const chunk of stream) {
|
|
3057
3147
|
const chunkRecord = chunk;
|
|
3058
3148
|
const candidates = chunkRecord.candidates;
|
|
@@ -3075,18 +3165,28 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3075
3165
|
usageMetadata.candidatesTokenCount > 0) {
|
|
3076
3166
|
outputTokens = usageMetadata.candidatesTokenCount;
|
|
3077
3167
|
}
|
|
3168
|
+
if (usageMetadata.thoughtsTokenCount !== undefined &&
|
|
3169
|
+
usageMetadata.thoughtsTokenCount > 0) {
|
|
3170
|
+
reasoningTokens = usageMetadata.thoughtsTokenCount;
|
|
3171
|
+
}
|
|
3078
3172
|
}
|
|
3079
3173
|
}
|
|
3080
3174
|
const text = parts
|
|
3081
3175
|
.filter((part) => typeof part.text === "string")
|
|
3082
3176
|
.map((part) => part.text)
|
|
3083
3177
|
.join("");
|
|
3084
|
-
return {
|
|
3178
|
+
return {
|
|
3179
|
+
text,
|
|
3180
|
+
finishReason,
|
|
3181
|
+
inputTokens,
|
|
3182
|
+
outputTokens,
|
|
3183
|
+
reasoningTokens,
|
|
3184
|
+
};
|
|
3085
3185
|
})(), timeoutMs, "Gemini synthesis call timed out");
|
|
3086
3186
|
}
|
|
3087
3187
|
catch (error) {
|
|
3088
3188
|
logger.warn("[GoogleVertex] Tools-disabled synthesis call failed; falling back to placeholder", { error: error instanceof Error ? error.message : String(error) });
|
|
3089
|
-
return { text: "", inputTokens: 0, outputTokens: 0 };
|
|
3189
|
+
return { text: "", inputTokens: 0, outputTokens: 0, reasoningTokens: 0 };
|
|
3090
3190
|
}
|
|
3091
3191
|
}
|
|
3092
3192
|
/**
|
|
@@ -3651,7 +3751,14 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3651
3751
|
turnCacheUsage.creation1h += stepCacheCreation1h;
|
|
3652
3752
|
usage.input += response.usage?.input_tokens || 0;
|
|
3653
3753
|
usage.output += response.usage?.output_tokens || 0;
|
|
3654
|
-
|
|
3754
|
+
// Anthropic's input_tokens is only the UNCACHED remainder; cache
|
|
3755
|
+
// reads/writes are billed tokens reported separately, so the
|
|
3756
|
+
// total must include them (matches proxyTracer + anthropic.ts).
|
|
3757
|
+
usage.total =
|
|
3758
|
+
usage.input +
|
|
3759
|
+
usage.output +
|
|
3760
|
+
turnCacheUsage.read +
|
|
3761
|
+
turnCacheUsage.creation;
|
|
3655
3762
|
lastStopReason = response.stop_reason;
|
|
3656
3763
|
// Feed the context guard the FULL prompt size of this call
|
|
3657
3764
|
// (uncached input + cache reads/writes).
|
|
@@ -4102,7 +4209,6 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4102
4209
|
activeStream = undefined;
|
|
4103
4210
|
usage.input += response.usage?.input_tokens || 0;
|
|
4104
4211
|
usage.output += response.usage?.output_tokens || 0;
|
|
4105
|
-
usage.total = usage.input + usage.output;
|
|
4106
4212
|
turnCacheUsage.read +=
|
|
4107
4213
|
response.usage?.cache_read_input_tokens ?? 0;
|
|
4108
4214
|
turnCacheUsage.creation +=
|
|
@@ -4111,6 +4217,13 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4111
4217
|
response.usage?.cache_creation?.ephemeral_5m_input_tokens ?? 0;
|
|
4112
4218
|
turnCacheUsage.creation1h +=
|
|
4113
4219
|
response.usage?.cache_creation?.ephemeral_1h_input_tokens ?? 0;
|
|
4220
|
+
// Total counts cache reads/writes — input_tokens is only the
|
|
4221
|
+
// uncached remainder.
|
|
4222
|
+
usage.total =
|
|
4223
|
+
usage.input +
|
|
4224
|
+
usage.output +
|
|
4225
|
+
turnCacheUsage.read +
|
|
4226
|
+
turnCacheUsage.creation;
|
|
4114
4227
|
lastStopReason = response.stop_reason;
|
|
4115
4228
|
const forcedFinalResult = response.content.find((block) => block.type === "tool_use" && block.name === "final_result");
|
|
4116
4229
|
if (forcedFinalResult) {
|
|
@@ -4196,7 +4309,6 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4196
4309
|
activeStream = undefined;
|
|
4197
4310
|
usage.input += response.usage?.input_tokens || 0;
|
|
4198
4311
|
usage.output += response.usage?.output_tokens || 0;
|
|
4199
|
-
usage.total = usage.input + usage.output;
|
|
4200
4312
|
turnCacheUsage.read +=
|
|
4201
4313
|
response.usage?.cache_read_input_tokens ?? 0;
|
|
4202
4314
|
turnCacheUsage.creation +=
|
|
@@ -4207,6 +4319,13 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
4207
4319
|
turnCacheUsage.creation1h +=
|
|
4208
4320
|
response.usage?.cache_creation?.ephemeral_1h_input_tokens ??
|
|
4209
4321
|
0;
|
|
4322
|
+
// Total counts cache reads/writes — input_tokens is only the
|
|
4323
|
+
// uncached remainder.
|
|
4324
|
+
usage.total =
|
|
4325
|
+
usage.input +
|
|
4326
|
+
usage.output +
|
|
4327
|
+
turnCacheUsage.read +
|
|
4328
|
+
turnCacheUsage.creation;
|
|
4210
4329
|
lastStopReason = response.stop_reason;
|
|
4211
4330
|
const backstopText = response.content
|
|
4212
4331
|
.filter((block) => block.type === "text")
|
|
@@ -5408,7 +5527,13 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5408
5527
|
usage: {
|
|
5409
5528
|
input: totalInputTokens,
|
|
5410
5529
|
output: totalOutputTokens,
|
|
5411
|
-
|
|
5530
|
+
// Anthropic's input_tokens is only the UNCACHED remainder; cache
|
|
5531
|
+
// reads/writes are billed tokens reported separately, so the total
|
|
5532
|
+
// must include them (matches proxyTracer + anthropic.ts).
|
|
5533
|
+
total: totalInputTokens +
|
|
5534
|
+
totalOutputTokens +
|
|
5535
|
+
totalCacheReadTokens +
|
|
5536
|
+
totalCacheCreationTokens,
|
|
5412
5537
|
...(totalCacheReadTokens > 0 && {
|
|
5413
5538
|
cacheReadTokens: totalCacheReadTokens,
|
|
5414
5539
|
}),
|
|
@@ -283,12 +283,23 @@ export class LiteLLMProvider extends OpenAIChatCompletionsProvider {
|
|
|
283
283
|
};
|
|
284
284
|
return {
|
|
285
285
|
onUsage: (usage) => {
|
|
286
|
-
|
|
286
|
+
// promptTokens is the uncached remainder — the attribute reports the
|
|
287
|
+
// full prompt (uncached + cache read/creation), and the cache fields
|
|
288
|
+
// let calculateCost price tiers.
|
|
289
|
+
span.setAttribute("gen_ai.usage.input_tokens", usage.promptTokens +
|
|
290
|
+
(usage.cacheReadTokens ?? 0) +
|
|
291
|
+
(usage.cacheCreationTokens ?? 0));
|
|
287
292
|
span.setAttribute("gen_ai.usage.output_tokens", usage.completionTokens);
|
|
288
|
-
const cost = calculateCost(this.providerName,
|
|
293
|
+
const cost = calculateCost(this.providerName, modelId, {
|
|
289
294
|
input: usage.promptTokens,
|
|
290
295
|
output: usage.completionTokens,
|
|
291
296
|
total: usage.totalTokens,
|
|
297
|
+
...(usage.cacheReadTokens
|
|
298
|
+
? { cacheReadTokens: usage.cacheReadTokens }
|
|
299
|
+
: {}),
|
|
300
|
+
...(usage.cacheCreationTokens
|
|
301
|
+
? { cacheCreationTokens: usage.cacheCreationTokens }
|
|
302
|
+
: {}),
|
|
292
303
|
});
|
|
293
304
|
if (cost && cost > 0) {
|
|
294
305
|
span.setAttribute("neurolink.cost", cost);
|
package/dist/providers/openAI.js
CHANGED
|
@@ -157,12 +157,23 @@ export class OpenAIProvider extends OpenAIChatCompletionsProvider {
|
|
|
157
157
|
};
|
|
158
158
|
return {
|
|
159
159
|
onUsage: (usage) => {
|
|
160
|
-
|
|
160
|
+
// promptTokens is the uncached remainder — the attribute reports the
|
|
161
|
+
// full prompt (uncached + cache read/creation), and the cache fields
|
|
162
|
+
// let calculateCost price tiers.
|
|
163
|
+
span.setAttribute("gen_ai.usage.input_tokens", usage.promptTokens +
|
|
164
|
+
(usage.cacheReadTokens ?? 0) +
|
|
165
|
+
(usage.cacheCreationTokens ?? 0));
|
|
161
166
|
span.setAttribute("gen_ai.usage.output_tokens", usage.completionTokens);
|
|
162
|
-
const cost = calculateCost(this.providerName,
|
|
167
|
+
const cost = calculateCost(this.providerName, modelId, {
|
|
163
168
|
input: usage.promptTokens,
|
|
164
169
|
output: usage.completionTokens,
|
|
165
170
|
total: usage.totalTokens,
|
|
171
|
+
...(usage.cacheReadTokens
|
|
172
|
+
? { cacheReadTokens: usage.cacheReadTokens }
|
|
173
|
+
: {}),
|
|
174
|
+
...(usage.cacheCreationTokens
|
|
175
|
+
? { cacheCreationTokens: usage.cacheCreationTokens }
|
|
176
|
+
: {}),
|
|
166
177
|
});
|
|
167
178
|
if (cost && cost > 0) {
|
|
168
179
|
span.setAttribute("neurolink.cost", cost);
|
|
@@ -505,8 +505,21 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
505
505
|
usage: {
|
|
506
506
|
inputTokens: {
|
|
507
507
|
total: json.usage?.prompt_tokens,
|
|
508
|
-
|
|
509
|
-
cacheRead
|
|
508
|
+
// cached_tokens is OVERLAPPING (a subset of prompt_tokens), so
|
|
509
|
+
// noCache is the remainder and cacheRead is clamped to the
|
|
510
|
+
// prompt total — some gateways report inconsistent usage where
|
|
511
|
+
// cached_tokens exceeds prompt_tokens (mirrors the
|
|
512
|
+
// reasoning_tokens clamp on the output side below).
|
|
513
|
+
noCache: json.usage?.prompt_tokens !== undefined &&
|
|
514
|
+
json.usage?.prompt_tokens_details?.cached_tokens !== undefined
|
|
515
|
+
? Math.max(0, json.usage.prompt_tokens -
|
|
516
|
+
json.usage.prompt_tokens_details.cached_tokens)
|
|
517
|
+
: json.usage?.prompt_tokens,
|
|
518
|
+
cacheRead: json.usage?.prompt_tokens !== undefined &&
|
|
519
|
+
json.usage?.prompt_tokens_details?.cached_tokens !== undefined
|
|
520
|
+
? Math.min(json.usage.prompt_tokens_details.cached_tokens, json.usage.prompt_tokens)
|
|
521
|
+
: json.usage?.prompt_tokens_details?.cached_tokens,
|
|
522
|
+
// OpenAI-style APIs do not report cache writes.
|
|
510
523
|
cacheWrite: undefined,
|
|
511
524
|
},
|
|
512
525
|
outputTokens: {
|
|
@@ -727,9 +740,33 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
727
740
|
}
|
|
728
741
|
async runStreamLoop(args) {
|
|
729
742
|
const { maxSteps, modelId, url, fetchImpl, abortSignal, options, conversation, openAITools, openAIToolChoice, toolsRecord, toolNameFromWire, emitter, toolsUsed, toolExecutionSummaries, pushChunk, resolveUsage, resolveFinish, } = args;
|
|
743
|
+
// Hoisted above the try so the catch can resolve the usage accumulated
|
|
744
|
+
// by steps that completed BEFORE the failure — those steps were billed.
|
|
745
|
+
let stepFinish = null;
|
|
746
|
+
let stepUsage;
|
|
747
|
+
// Aggregated DeferredUsage from whatever accumulated in stepUsage.
|
|
748
|
+
// prompt_tokens is OVERLAPPING with cached_tokens (a subset), so the
|
|
749
|
+
// uncached remainder goes in promptTokens and the cached part in
|
|
750
|
+
// cacheReadTokens — the non-overlapping convention extractTokenUsage
|
|
751
|
+
// and calculateCost expect. reasoning_tokens stays a subset of
|
|
752
|
+
// completionTokens (informational).
|
|
753
|
+
const toDeferredUsage = () => {
|
|
754
|
+
const promptTokens = stepUsage?.prompt_tokens ?? 0;
|
|
755
|
+
const completionTokens = stepUsage?.completion_tokens ?? 0;
|
|
756
|
+
const cachedTokens = Math.min(stepUsage?.prompt_tokens_details?.cached_tokens ?? 0, promptTokens);
|
|
757
|
+
// Clamped like cached_tokens above: reasoning is a SUBSET of
|
|
758
|
+
// completion_tokens, but some gateways report inconsistent values.
|
|
759
|
+
const reasoningTokens = Math.min(Math.max(0, stepUsage?.completion_tokens_details?.reasoning_tokens ?? 0), completionTokens);
|
|
760
|
+
return {
|
|
761
|
+
promptTokens: promptTokens - cachedTokens,
|
|
762
|
+
completionTokens,
|
|
763
|
+
// Gateways that omit total_tokens must not collapse to 0.
|
|
764
|
+
totalTokens: stepUsage?.total_tokens || promptTokens + completionTokens,
|
|
765
|
+
...(cachedTokens > 0 ? { cacheReadTokens: cachedTokens } : {}),
|
|
766
|
+
...(reasoningTokens > 0 ? { reasoningTokens } : {}),
|
|
767
|
+
};
|
|
768
|
+
};
|
|
730
769
|
try {
|
|
731
|
-
let stepFinish = null;
|
|
732
|
-
let stepUsage;
|
|
733
770
|
// May grow mid-turn: hydrated tools with wire-unsafe names need
|
|
734
771
|
// reverse-mapping even when the initial name set required none.
|
|
735
772
|
let effectiveToolNameFromWire = toolNameFromWire;
|
|
@@ -787,11 +824,7 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
787
824
|
options,
|
|
788
825
|
});
|
|
789
826
|
}
|
|
790
|
-
resolveUsage(
|
|
791
|
-
promptTokens: stepUsage?.prompt_tokens ?? 0,
|
|
792
|
-
completionTokens: stepUsage?.completion_tokens ?? 0,
|
|
793
|
-
totalTokens: stepUsage?.total_tokens ?? 0,
|
|
794
|
-
});
|
|
827
|
+
resolveUsage(toDeferredUsage());
|
|
795
828
|
resolveFinish(stepFinish ?? "stop");
|
|
796
829
|
pushChunk({ done: true });
|
|
797
830
|
return {
|
|
@@ -803,7 +836,9 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
|
|
|
803
836
|
logger.error(`${this.providerName}: Stream error`, {
|
|
804
837
|
error: err instanceof Error ? err.message : String(err),
|
|
805
838
|
});
|
|
806
|
-
|
|
839
|
+
// Steps that completed before the failure were billed — report them
|
|
840
|
+
// instead of zeroing the whole turn.
|
|
841
|
+
resolveUsage(toDeferredUsage());
|
|
807
842
|
resolveFinish("error");
|
|
808
843
|
pushChunk({ done: true });
|
|
809
844
|
throw err;
|
|
@@ -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, DeferredUsage, Tool } from "../types/index.js";
|
|
16
|
+
import type { OpenAICompatBuildBodyArgs, OpenAICompatChatMessage, OpenAICompatChatRequest, OpenAICompatChatTool, OpenAICompatMessage, OpenAICompatMessageContent, OpenAICompatResponseFormat, OpenAICompatSSEResult, OpenAICompatStreamChunk, OpenAICompatToolChoiceWire, OpenAICompatUsage, OpenAICompatV3CallToolChoice, OpenAICompatV3CallTools, DeferredUsage, Tool } from "../types/index.js";
|
|
17
17
|
export declare const stripTrailingSlash: (s: string) => string;
|
|
18
18
|
/**
|
|
19
19
|
* Build a bijective original ↔ wire tool-name map. Returns undefined when
|
|
@@ -71,16 +71,4 @@ export declare const createChunkQueue: () => {
|
|
|
71
71
|
pushChunk: (c: OpenAICompatStreamChunk) => void;
|
|
72
72
|
nextChunk: () => Promise<OpenAICompatStreamChunk>;
|
|
73
73
|
};
|
|
74
|
-
export declare const mergeUsage: (a:
|
|
75
|
-
prompt_tokens?: number;
|
|
76
|
-
completion_tokens?: number;
|
|
77
|
-
total_tokens?: number;
|
|
78
|
-
} | undefined, b: {
|
|
79
|
-
prompt_tokens?: number;
|
|
80
|
-
completion_tokens?: number;
|
|
81
|
-
total_tokens?: number;
|
|
82
|
-
} | undefined) => {
|
|
83
|
-
prompt_tokens?: number;
|
|
84
|
-
completion_tokens?: number;
|
|
85
|
-
total_tokens?: number;
|
|
86
|
-
} | undefined;
|
|
74
|
+
export declare const mergeUsage: (a: OpenAICompatUsage | undefined, b: OpenAICompatUsage | undefined) => OpenAICompatUsage | undefined;
|