@@ -103,6 +103,14 @@ describe('doGenerate', () => {
103
103
prompt_tokens ?: number ;
104
104
total_tokens ?: number ;
105
105
completion_tokens ?: number ;
106
+ prompt_tokens_details ?: {
107
+ cached_tokens ?: number ;
108
+ } ;
109
+ completion_tokens_details ?: {
110
+ reasoning_tokens ?: number ;
111
+ accepted_prediction_tokens ?: number ;
112
+ rejected_prediction_tokens ?: number ;
113
+ } ;
106
114
} ;
107
115
finish_reason ?: string ;
108
116
created ?: number ;
@@ -861,6 +869,86 @@ describe('doGenerate', () => {
861
869
body : '{"model":"grok-beta","messages":[{"role":"user","content":"Hello"}]}' ,
862
870
} ) ;
863
871
} ) ;
872
+
873
+ describe ( 'usage details' , ( ) => {
874
+ it ( 'should extract detailed token usage when available' , async ( ) => {
875
+ prepareJsonResponse ( {
876
+ content : '' ,
877
+ usage : {
878
+ prompt_tokens : 20 ,
879
+ completion_tokens : 30 ,
880
+ prompt_tokens_details : {
881
+ cached_tokens : 5 ,
882
+ } ,
883
+ completion_tokens_details : {
884
+ reasoning_tokens : 10 ,
885
+ accepted_prediction_tokens : 15 ,
886
+ rejected_prediction_tokens : 5 ,
887
+ } ,
888
+ } ,
889
+ } ) ;
890
+
891
+ const result = await model . doGenerate ( {
892
+ inputFormat : 'prompt' ,
893
+ mode : { type : 'regular' } ,
894
+ prompt : TEST_PROMPT ,
895
+ } ) ;
896
+
897
+ expect ( result . providerMetadata ! [ 'test-provider' ] ) . toStrictEqual ( {
898
+ cachedPromptTokens : 5 ,
899
+ reasoningTokens : 10 ,
900
+ acceptedPredictionTokens : 15 ,
901
+ rejectedPredictionTokens : 5 ,
902
+ } ) ;
903
+ } ) ;
904
+
905
+ it ( 'should handle missing token details' , async ( ) => {
906
+ prepareJsonResponse ( {
907
+ content : '' ,
908
+ usage : {
909
+ prompt_tokens : 20 ,
910
+ completion_tokens : 30 ,
911
+ // No token details provided
912
+ } ,
913
+ } ) ;
914
+
915
+ const result = await model . doGenerate ( {
916
+ inputFormat : 'prompt' ,
917
+ mode : { type : 'regular' } ,
918
+ prompt : TEST_PROMPT ,
919
+ } ) ;
920
+
921
+ expect ( result . providerMetadata ! [ 'test-provider' ] ) . toStrictEqual ( { } ) ;
922
+ } ) ;
923
+
924
+ it ( 'should handle partial token details' , async ( ) => {
925
+ prepareJsonResponse ( {
926
+ content : '' ,
927
+ usage : {
928
+ prompt_tokens : 20 ,
929
+ completion_tokens : 30 ,
930
+ prompt_tokens_details : {
931
+ cached_tokens : 5 ,
932
+ } ,
933
+ completion_tokens_details : {
934
+ // Only reasoning tokens provided
935
+ reasoning_tokens : 10 ,
936
+ } ,
937
+ } ,
938
+ } ) ;
939
+
940
+ const result = await model . doGenerate ( {
941
+ inputFormat : 'prompt' ,
942
+ mode : { type : 'regular' } ,
943
+ prompt : TEST_PROMPT ,
944
+ } ) ;
945
+
946
+ expect ( result . providerMetadata ! [ 'test-provider' ] ) . toStrictEqual ( {
947
+ cachedPromptTokens : 5 ,
948
+ reasoningTokens : 10 ,
949
+ } ) ;
950
+ } ) ;
951
+ } ) ;
864
952
} ) ;
865
953
866
954
describe ( 'doStream' , ( ) => {
@@ -924,6 +1012,9 @@ describe('doStream', () => {
924
1012
type : 'finish' ,
925
1013
finishReason : 'stop' ,
926
1014
usage : { promptTokens : 18 , completionTokens : 439 } ,
1015
+ providerMetadata : {
1016
+ 'test-provider' : { } ,
1017
+ } ,
927
1018
} ,
928
1019
] ) ;
929
1020
} ) ;
@@ -980,6 +1071,9 @@ describe('doStream', () => {
980
1071
type : 'finish' ,
981
1072
finishReason : 'stop' ,
982
1073
usage : { promptTokens : 18 , completionTokens : 439 } ,
1074
+ providerMetadata : {
1075
+ 'test-provider' : { } ,
1076
+ } ,
983
1077
} ,
984
1078
] ) ;
985
1079
} ) ;
@@ -1109,6 +1203,9 @@ describe('doStream', () => {
1109
1203
type : 'finish' ,
1110
1204
finishReason : 'tool-calls' ,
1111
1205
usage : { promptTokens : 18 , completionTokens : 439 } ,
1206
+ providerMetadata : {
1207
+ 'test-provider' : { } ,
1208
+ } ,
1112
1209
} ,
1113
1210
] ) ;
1114
1211
} ) ;
@@ -1245,6 +1342,9 @@ describe('doStream', () => {
1245
1342
type : 'finish' ,
1246
1343
finishReason : 'tool-calls' ,
1247
1344
usage : { promptTokens : 18 , completionTokens : 439 } ,
1345
+ providerMetadata : {
1346
+ 'test-provider' : { } ,
1347
+ } ,
1248
1348
} ,
1249
1349
] ) ;
1250
1350
} ) ;
@@ -1370,6 +1470,9 @@ describe('doStream', () => {
1370
1470
type : 'finish' ,
1371
1471
finishReason : 'tool-calls' ,
1372
1472
usage : { promptTokens : 226 , completionTokens : 20 } ,
1473
+ providerMetadata : {
1474
+ 'test-provider' : { } ,
1475
+ } ,
1373
1476
} ,
1374
1477
] ) ;
1375
1478
} ) ;
@@ -1436,6 +1539,9 @@ describe('doStream', () => {
1436
1539
type : 'finish' ,
1437
1540
finishReason : 'tool-calls' ,
1438
1541
usage : { promptTokens : 18 , completionTokens : 439 } ,
1542
+ providerMetadata : {
1543
+ 'test-provider' : { } ,
1544
+ } ,
1439
1545
} ,
1440
1546
] ) ;
1441
1547
} ) ;
@@ -1468,6 +1574,9 @@ describe('doStream', () => {
1468
1574
promptTokens : NaN ,
1469
1575
completionTokens : NaN ,
1470
1576
} ,
1577
+ providerMetadata : {
1578
+ 'test-provider' : { } ,
1579
+ } ,
1471
1580
} ,
1472
1581
] ) ;
1473
1582
} ) ;
@@ -1495,6 +1604,9 @@ describe('doStream', () => {
1495
1604
completionTokens : NaN ,
1496
1605
promptTokens : NaN ,
1497
1606
} ,
1607
+ providerMetadata : {
1608
+ 'test-provider' : { } ,
1609
+ } ,
1498
1610
} ) ;
1499
1611
} ) ;
1500
1612
@@ -1621,6 +1733,92 @@ describe('doStream', () => {
1621
1733
body : '{"model":"grok-beta","messages":[{"role":"user","content":"Hello"}],"stream":true}' ,
1622
1734
} ) ;
1623
1735
} ) ;
1736
+
1737
+ describe ( 'usage details in streaming' , ( ) => {
1738
+ it ( 'should extract detailed token usage from stream finish' , async ( ) => {
1739
+ server . urls [ 'https://my.api.com/v1/chat/completions' ] . response = {
1740
+ type : 'stream-chunks' ,
1741
+ chunks : [
1742
+ `data: {"id":"chat-id","choices":[{"delta":{"content":"Hello"}}]}\n\n` ,
1743
+ `data: {"choices":[{"delta":{},"finish_reason":"stop"}],` +
1744
+ `"usage":{"prompt_tokens":20,"completion_tokens":30,` +
1745
+ `"prompt_tokens_details":{"cached_tokens":5},` +
1746
+ `"completion_tokens_details":{` +
1747
+ `"reasoning_tokens":10,` +
1748
+ `"accepted_prediction_tokens":15,` +
1749
+ `"rejected_prediction_tokens":5}}}\n\n` ,
1750
+ 'data: [DONE]\n\n' ,
1751
+ ] ,
1752
+ } ;
1753
+
1754
+ const { stream } = await model . doStream ( {
1755
+ inputFormat : 'prompt' ,
1756
+ mode : { type : 'regular' } ,
1757
+ prompt : TEST_PROMPT ,
1758
+ } ) ;
1759
+
1760
+ const parts = await convertReadableStreamToArray ( stream ) ;
1761
+ const finishPart = parts . find ( part => part . type === 'finish' ) ;
1762
+
1763
+ expect ( finishPart ?. providerMetadata ! [ 'test-provider' ] ) . toStrictEqual ( {
1764
+ cachedPromptTokens : 5 ,
1765
+ reasoningTokens : 10 ,
1766
+ acceptedPredictionTokens : 15 ,
1767
+ rejectedPredictionTokens : 5 ,
1768
+ } ) ;
1769
+ } ) ;
1770
+
1771
+ it ( 'should handle missing token details in stream' , async ( ) => {
1772
+ server . urls [ 'https://my.api.com/v1/chat/completions' ] . response = {
1773
+ type : 'stream-chunks' ,
1774
+ chunks : [
1775
+ `data: {"id":"chat-id","choices":[{"delta":{"content":"Hello"}}]}\n\n` ,
1776
+ `data: {"choices":[{"delta":{},"finish_reason":"stop"}],` +
1777
+ `"usage":{"prompt_tokens":20,"completion_tokens":30}}\n\n` ,
1778
+ 'data: [DONE]\n\n' ,
1779
+ ] ,
1780
+ } ;
1781
+
1782
+ const { stream } = await model . doStream ( {
1783
+ inputFormat : 'prompt' ,
1784
+ mode : { type : 'regular' } ,
1785
+ prompt : TEST_PROMPT ,
1786
+ } ) ;
1787
+
1788
+ const parts = await convertReadableStreamToArray ( stream ) ;
1789
+ const finishPart = parts . find ( part => part . type === 'finish' ) ;
1790
+
1791
+ expect ( finishPart ?. providerMetadata ! [ 'test-provider' ] ) . toStrictEqual ( { } ) ;
1792
+ } ) ;
1793
+
1794
+ it ( 'should handle partial token details in stream' , async ( ) => {
1795
+ server . urls [ 'https://my.api.com/v1/chat/completions' ] . response = {
1796
+ type : 'stream-chunks' ,
1797
+ chunks : [
1798
+ `data: {"id":"chat-id","choices":[{"delta":{"content":"Hello"}}]}\n\n` ,
1799
+ `data: {"choices":[{"delta":{},"finish_reason":"stop"}],` +
1800
+ `"usage":{"prompt_tokens":20,"completion_tokens":30,` +
1801
+ `"prompt_tokens_details":{"cached_tokens":5},` +
1802
+ `"completion_tokens_details":{"reasoning_tokens":10}}}\n\n` ,
1803
+ 'data: [DONE]\n\n' ,
1804
+ ] ,
1805
+ } ;
1806
+
1807
+ const { stream } = await model . doStream ( {
1808
+ inputFormat : 'prompt' ,
1809
+ mode : { type : 'regular' } ,
1810
+ prompt : TEST_PROMPT ,
1811
+ } ) ;
1812
+
1813
+ const parts = await convertReadableStreamToArray ( stream ) ;
1814
+ const finishPart = parts . find ( part => part . type === 'finish' ) ;
1815
+
1816
+ expect ( finishPart ?. providerMetadata ! [ 'test-provider' ] ) . toStrictEqual ( {
1817
+ cachedPromptTokens : 5 ,
1818
+ reasoningTokens : 10 ,
1819
+ } ) ;
1820
+ } ) ;
1821
+ } ) ;
1624
1822
} ) ;
1625
1823
1626
1824
describe ( 'doStream simulated streaming' , ( ) => {
@@ -1709,7 +1907,9 @@ describe('doStream simulated streaming', () => {
1709
1907
finishReason : 'stop' ,
1710
1908
usage : { promptTokens : 4 , completionTokens : 30 } ,
1711
1909
logprobs : undefined ,
1712
- providerMetadata : undefined ,
1910
+ providerMetadata : {
1911
+ 'test-provider' : { } ,
1912
+ } ,
1713
1913
} ,
1714
1914
] ) ;
1715
1915
} ) ;
@@ -1751,7 +1951,9 @@ describe('doStream simulated streaming', () => {
1751
1951
finishReason : 'stop' ,
1752
1952
usage : { promptTokens : 4 , completionTokens : 30 } ,
1753
1953
logprobs : undefined ,
1754
- providerMetadata : undefined ,
1954
+ providerMetadata : {
1955
+ 'test-provider' : { } ,
1956
+ } ,
1755
1957
} ,
1756
1958
] ) ;
1757
1959
} ) ;
@@ -1815,7 +2017,9 @@ describe('doStream simulated streaming', () => {
1815
2017
finishReason : 'stop' ,
1816
2018
usage : { promptTokens : 4 , completionTokens : 30 } ,
1817
2019
logprobs : undefined ,
1818
- providerMetadata : undefined ,
2020
+ providerMetadata : {
2021
+ 'test-provider' : { } ,
2022
+ } ,
1819
2023
} ,
1820
2024
] ) ;
1821
2025
} ) ;
@@ -1832,7 +2036,7 @@ describe('metadata extraction', () => {
1832
2036
return undefined ;
1833
2037
}
1834
2038
return {
1835
- test : {
2039
+ ' test-provider' : {
1836
2040
value : parsedBody . test_field as string ,
1837
2041
} ,
1838
2042
} ;
@@ -1856,7 +2060,7 @@ describe('metadata extraction', () => {
1856
2060
buildMetadata : ( ) =>
1857
2061
accumulatedValue
1858
2062
? {
1859
- test : {
2063
+ ' test-provider' : {
1860
2064
value : accumulatedValue ,
1861
2065
} ,
1862
2066
}
@@ -1905,7 +2109,7 @@ describe('metadata extraction', () => {
1905
2109
} ) ;
1906
2110
1907
2111
expect ( result . providerMetadata ) . toEqual ( {
1908
- test : {
2112
+ ' test-provider' : {
1909
2113
value : 'test_value' ,
1910
2114
} ,
1911
2115
} ) ;
@@ -1947,7 +2151,7 @@ describe('metadata extraction', () => {
1947
2151
const finishPart = parts . find ( part => part . type === 'finish' ) ;
1948
2152
1949
2153
expect ( finishPart ?. providerMetadata ) . toEqual ( {
1950
- test : {
2154
+ ' test-provider' : {
1951
2155
value : 'test_value' ,
1952
2156
} ,
1953
2157
} ) ;
0 commit comments