@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
|
@@ -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);
|
|
@@ -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;
|
|
@@ -673,10 +673,29 @@ 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
|
};
|
|
682
701
|
//# sourceMappingURL=openaiChatCompletionsClient.js.map
|
|
@@ -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
|
*
|