@@ -29,7 +29,6 @@ import {
29
29
getApiProtocol ,
30
30
getModelId ,
31
31
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT ,
32
- DEFAULT_USAGE_COLLECTION_TIMEOUT_MS ,
33
32
isBlockingAsk ,
34
33
} from "@roo-code/types"
35
34
import { TelemetryService } from "@roo-code/telemetry"
@@ -1566,10 +1565,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
1566
1565
// of prices in tasks from history (it's worth removing a few months
1567
1566
// from now).
1568
1567
const updateApiReqMsg = ( cancelReason ?: ClineApiReqCancelReason , streamingFailedMessage ?: string ) => {
1569
- if ( lastApiReqIndex < 0 || ! this . clineMessages [ lastApiReqIndex ] ) {
1570
- return
1571
- }
1572
-
1573
1568
const existingData = JSON . parse ( this . clineMessages [ lastApiReqIndex ] . text || "{}" )
1574
1569
this . clineMessages [ lastApiReqIndex ] . text = JSON . stringify ( {
1575
1570
...existingData ,
@@ -1660,11 +1655,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
1660
1655
this . isStreaming = true
1661
1656
1662
1657
try {
1663
- const iterator = stream [ Symbol . asyncIterator ] ( )
1664
- let item = await iterator . next ( )
1665
- while ( ! item . done ) {
1666
- const chunk = item . value
1667
- item = await iterator . next ( )
1658
+ for await ( const chunk of stream ) {
1668
1659
if ( ! chunk ) {
1669
1660
// Sometimes chunk is undefined, no idea that can cause
1670
1661
// it, but this workaround seems to fix it.
@@ -1732,165 +1723,16 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
1732
1723
break
1733
1724
}
1734
1725
1726
+ // PREV: We need to let the request finish for openrouter to
1727
+ // get generation details.
1728
+ // UPDATE: It's better UX to interrupt the request at the
1729
+ // cost of the API cost not being retrieved.
1735
1730
if ( this . didAlreadyUseTool ) {
1736
1731
assistantMessage +=
1737
1732
"\n\n[Response interrupted by a tool use result. Only one tool may be used at a time and should be placed at the end of the message.]"
1738
1733
break
1739
1734
}
1740
1735
}
1741
-
1742
- // Create a copy of current token values to avoid race conditions
1743
- const currentTokens = {
1744
- input : inputTokens ,
1745
- output : outputTokens ,
1746
- cacheWrite : cacheWriteTokens ,
1747
- cacheRead : cacheReadTokens ,
1748
- total : totalCost ,
1749
- }
1750
-
1751
- const drainStreamInBackgroundToFindAllUsage = async ( apiReqIndex : number ) => {
1752
- const timeoutMs = DEFAULT_USAGE_COLLECTION_TIMEOUT_MS
1753
- const startTime = Date . now ( )
1754
- const modelId = getModelId ( this . apiConfiguration )
1755
-
1756
- // Local variables to accumulate usage data without affecting the main flow
1757
- let bgInputTokens = currentTokens . input
1758
- let bgOutputTokens = currentTokens . output
1759
- let bgCacheWriteTokens = currentTokens . cacheWrite
1760
- let bgCacheReadTokens = currentTokens . cacheRead
1761
- let bgTotalCost = currentTokens . total
1762
-
1763
- // Helper function to capture telemetry and update messages
1764
- const captureUsageData = async (
1765
- tokens : {
1766
- input : number
1767
- output : number
1768
- cacheWrite : number
1769
- cacheRead : number
1770
- total ?: number
1771
- } ,
1772
- messageIndex : number = apiReqIndex ,
1773
- ) => {
1774
- if ( tokens . input > 0 || tokens . output > 0 || tokens . cacheWrite > 0 || tokens . cacheRead > 0 ) {
1775
- // Update the shared variables atomically
1776
- inputTokens = tokens . input
1777
- outputTokens = tokens . output
1778
- cacheWriteTokens = tokens . cacheWrite
1779
- cacheReadTokens = tokens . cacheRead
1780
- totalCost = tokens . total
1781
-
1782
- // Update the API request message with the latest usage data
1783
- updateApiReqMsg ( )
1784
- await this . saveClineMessages ( )
1785
-
1786
- // Update the specific message in the webview
1787
- const apiReqMessage = this . clineMessages [ messageIndex ]
1788
- if ( apiReqMessage ) {
1789
- await this . updateClineMessage ( apiReqMessage )
1790
- }
1791
-
1792
- // Capture telemetry
1793
- TelemetryService . instance . captureLlmCompletion ( this . taskId , {
1794
- inputTokens : tokens . input ,
1795
- outputTokens : tokens . output ,
1796
- cacheWriteTokens : tokens . cacheWrite ,
1797
- cacheReadTokens : tokens . cacheRead ,
1798
- cost :
1799
- tokens . total ??
1800
- calculateApiCostAnthropic (
1801
- this . api . getModel ( ) . info ,
1802
- tokens . input ,
1803
- tokens . output ,
1804
- tokens . cacheWrite ,
1805
- tokens . cacheRead ,
1806
- ) ,
1807
- } )
1808
- }
1809
- }
1810
-
1811
- try {
1812
- // Continue processing the original stream from where the main loop left off
1813
- let usageFound = false
1814
- let chunkCount = 0
1815
-
1816
- // Use the same iterator that the main loop was using
1817
- while ( ! item . done ) {
1818
- // Check for timeout
1819
- if ( Date . now ( ) - startTime > timeoutMs ) {
1820
- console . warn (
1821
- `[Background Usage Collection] Timed out after ${ timeoutMs } ms for model: ${ modelId } , processed ${ chunkCount } chunks` ,
1822
- )
1823
- // Clean up the iterator before breaking
1824
- if ( iterator . return ) {
1825
- await iterator . return ( undefined )
1826
- }
1827
- break
1828
- }
1829
-
1830
- const chunk = item . value
1831
- item = await iterator . next ( )
1832
- chunkCount ++
1833
-
1834
- if ( chunk && chunk . type === "usage" ) {
1835
- usageFound = true
1836
- bgInputTokens += chunk . inputTokens
1837
- bgOutputTokens += chunk . outputTokens
1838
- bgCacheWriteTokens += chunk . cacheWriteTokens ?? 0
1839
- bgCacheReadTokens += chunk . cacheReadTokens ?? 0
1840
- bgTotalCost = chunk . totalCost
1841
- }
1842
- }
1843
-
1844
- if (
1845
- usageFound ||
1846
- bgInputTokens > 0 ||
1847
- bgOutputTokens > 0 ||
1848
- bgCacheWriteTokens > 0 ||
1849
- bgCacheReadTokens > 0
1850
- ) {
1851
- // We have usage data either from a usage chunk or accumulated tokens
1852
- await captureUsageData (
1853
- {
1854
- input : bgInputTokens ,
1855
- output : bgOutputTokens ,
1856
- cacheWrite : bgCacheWriteTokens ,
1857
- cacheRead : bgCacheReadTokens ,
1858
- total : bgTotalCost ,
1859
- } ,
1860
- lastApiReqIndex ,
1861
- )
1862
- } else {
1863
- console . warn (
1864
- `[Background Usage Collection] Suspicious: request ${ apiReqIndex } is complete, but no usage info was found. Model: ${ modelId } ` ,
1865
- )
1866
- }
1867
- } catch ( error ) {
1868
- console . error ( "Error draining stream for usage data:" , error )
1869
- // Still try to capture whatever usage data we have collected so far
1870
- if (
1871
- bgInputTokens > 0 ||
1872
- bgOutputTokens > 0 ||
1873
- bgCacheWriteTokens > 0 ||
1874
- bgCacheReadTokens > 0
1875
- ) {
1876
- await captureUsageData (
1877
- {
1878
- input : bgInputTokens ,
1879
- output : bgOutputTokens ,
1880
- cacheWrite : bgCacheWriteTokens ,
1881
- cacheRead : bgCacheReadTokens ,
1882
- total : bgTotalCost ,
1883
- } ,
1884
- lastApiReqIndex ,
1885
- )
1886
- }
1887
- }
1888
- }
1889
-
1890
- // Start the background task and handle any errors
1891
- drainStreamInBackgroundToFindAllUsage ( lastApiReqIndex ) . catch ( ( error ) => {
1892
- console . error ( "Background usage collection failed:" , error )
1893
- } )
1894
1736
} catch ( error ) {
1895
1737
// Abandoned happens when extension is no longer waiting for the
1896
1738
// Cline instance to finish aborting (error is thrown here when
@@ -1924,6 +1766,24 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
1924
1766
this . isStreaming = false
1925
1767
}
1926
1768
1769
+ if ( inputTokens > 0 || outputTokens > 0 || cacheWriteTokens > 0 || cacheReadTokens > 0 ) {
1770
+ TelemetryService . instance . captureLlmCompletion ( this . taskId , {
1771
+ inputTokens,
1772
+ outputTokens,
1773
+ cacheWriteTokens,
1774
+ cacheReadTokens,
1775
+ cost :
1776
+ totalCost ??
1777
+ calculateApiCostAnthropic (
1778
+ this . api . getModel ( ) . info ,
1779
+ inputTokens ,
1780
+ outputTokens ,
1781
+ cacheWriteTokens ,
1782
+ cacheReadTokens ,
1783
+ ) ,
1784
+ } )
1785
+ }
1786
+
1927
1787
// Need to call here in case the stream was aborted.
1928
1788
if ( this . abort || this . abandoned ) {
1929
1789
throw new Error ( `[RooCode#recursivelyMakeRooRequests] task ${ this . taskId } .${ this . instanceId } aborted` )
@@ -1960,10 +1820,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
1960
1820
presentAssistantMessage ( this )
1961
1821
}
1962
1822
1963
- // Note: updateApiReqMsg() is now called from within drainStreamInBackgroundToFindAllUsage
1964
- // to ensure usage data is captured even when the stream is interrupted. The background task
1965
- // uses local variables to accumulate usage data before atomically updating the shared state.
1966
1823
await this . persistGpt5Metadata ( reasoningMessage )
1824
+
1825
+ updateApiReqMsg ( )
1967
1826
await this . saveClineMessages ( )
1968
1827
await this . providerRef . deref ( ) ?. postStateToWebview ( )
1969
1828
0 commit comments