@@ -1334,5 +1334,184 @@ describe("Cline", () => {
13341334 expect ( task . diffStrategy ) . toBeUndefined ( )
13351335 } )
13361336 } )
1337+
1338+ describe ( "finish method" , ( ) => {
1339+ it ( "should finalize all pending api_req_started messages with cost 0" , async ( ) => {
1340+ const [ cline , task ] = Task . create ( {
1341+ provider : mockProvider ,
1342+ apiConfiguration : mockApiConfig ,
1343+ task : "test task" ,
1344+ } )
1345+
1346+ // Mock saveClineMessages
1347+ const saveSpy = vi . spyOn ( cline as any , "saveClineMessages" ) . mockResolvedValue ( undefined )
1348+
1349+ // Set up messages with multiple api_req_started messages
1350+ cline . clineMessages = [
1351+ {
1352+ ts : Date . now ( ) ,
1353+ type : "say" ,
1354+ say : "text" ,
1355+ text : "Regular message" ,
1356+ } ,
1357+ {
1358+ ts : Date . now ( ) ,
1359+ type : "say" ,
1360+ say : "api_req_started" ,
1361+ text : JSON . stringify ( {
1362+ request : "test request 1" ,
1363+ tokensIn : 100 ,
1364+ tokensOut : 50 ,
1365+ // No cost - should be updated
1366+ } ) ,
1367+ } ,
1368+ {
1369+ ts : Date . now ( ) ,
1370+ type : "say" ,
1371+ say : "api_req_started" ,
1372+ text : JSON . stringify ( {
1373+ request : "test request 2" ,
1374+ tokensIn : 200 ,
1375+ tokensOut : 100 ,
1376+ cost : 0.001 , // Already has cost
1377+ } ) ,
1378+ } ,
1379+ {
1380+ ts : Date . now ( ) ,
1381+ type : "say" ,
1382+ say : "text" ,
1383+ text : "Another regular message" ,
1384+ } ,
1385+ {
1386+ ts : Date . now ( ) ,
1387+ type : "say" ,
1388+ say : "api_req_started" ,
1389+ text : JSON . stringify ( {
1390+ request : "test request 3" ,
1391+ tokensIn : 300 ,
1392+ tokensOut : 150 ,
1393+ // No cost or cancelReason - should be updated
1394+ } ) ,
1395+ } ,
1396+ ]
1397+
1398+ // Call finish
1399+ await cline . finish ( )
1400+
1401+ // Verify saveClineMessages was called
1402+ expect ( saveSpy ) . toHaveBeenCalled ( )
1403+
1404+ // Verify the messages were updated correctly
1405+ const messages = cline . clineMessages
1406+
1407+ // First api_req_started (index 1) had no cost, should now have cost 0
1408+ const msg1 = JSON . parse ( messages [ 1 ] . text || "{}" )
1409+ expect ( msg1 . cost ) . toBe ( 0 )
1410+ expect ( msg1 . request ) . toBe ( "test request 1" )
1411+
1412+ // Second api_req_started (index 2) already had cost, should be unchanged
1413+ const msg2 = JSON . parse ( messages [ 2 ] . text || "{}" )
1414+ expect ( msg2 . cost ) . toBe ( 0.001 )
1415+
1416+ // Last api_req_started (index 4) had no cost/cancelReason, should now have cost 0
1417+ const msg3 = JSON . parse ( messages [ 4 ] . text || "{}" )
1418+ expect ( msg3 . cost ) . toBe ( 0 )
1419+ expect ( msg3 . request ) . toBe ( "test request 3" )
1420+
1421+ // Verify that ALL api_req_started messages now have either cost or cancelReason
1422+ const apiReqMessages = messages . filter ( ( m ) => m . type === "say" && m . say === "api_req_started" )
1423+ for ( const msg of apiReqMessages ) {
1424+ const apiReqInfo = JSON . parse ( msg . text || "{}" )
1425+ const hasCost = apiReqInfo . cost !== undefined
1426+ const hasCancelReason = apiReqInfo . cancelReason !== undefined
1427+ expect ( hasCost || hasCancelReason ) . toBe ( true )
1428+ }
1429+
1430+ await cline . abortTask ( true )
1431+ await task . catch ( ( ) => { } )
1432+ } )
1433+
1434+ it ( "should not save if no api_req_started messages need updating" , async ( ) => {
1435+ const [ cline , task ] = Task . create ( {
1436+ provider : mockProvider ,
1437+ apiConfiguration : mockApiConfig ,
1438+ task : "test task" ,
1439+ } )
1440+
1441+ // Mock saveClineMessages
1442+ const saveSpy = vi . spyOn ( cline as any , "saveClineMessages" ) . mockResolvedValue ( undefined )
1443+
1444+ // Set up messages where all api_req_started already have cost or cancelReason
1445+ cline . clineMessages = [
1446+ {
1447+ ts : Date . now ( ) ,
1448+ type : "say" ,
1449+ say : "api_req_started" ,
1450+ text : JSON . stringify ( {
1451+ request : "test request 1" ,
1452+ tokensIn : 100 ,
1453+ tokensOut : 50 ,
1454+ cost : 0.001 , // Has cost
1455+ } ) ,
1456+ } ,
1457+ {
1458+ ts : Date . now ( ) ,
1459+ type : "say" ,
1460+ say : "api_req_started" ,
1461+ text : JSON . stringify ( {
1462+ request : "test request 2" ,
1463+ tokensIn : 200 ,
1464+ tokensOut : 100 ,
1465+ cancelReason : "User cancelled" , // Has cancelReason
1466+ } ) ,
1467+ } ,
1468+ ]
1469+
1470+ // Clear any calls from setup
1471+ saveSpy . mockClear ( )
1472+
1473+ // Call finish
1474+ await cline . finish ( )
1475+
1476+ // Verify saveClineMessages was NOT called since no updates were needed
1477+ expect ( saveSpy ) . not . toHaveBeenCalled ( )
1478+
1479+ await cline . abortTask ( true )
1480+ await task . catch ( ( ) => { } )
1481+ } )
1482+
1483+ it ( "should handle case with no api_req_started messages" , async ( ) => {
1484+ const [ cline , task ] = Task . create ( {
1485+ provider : mockProvider ,
1486+ apiConfiguration : mockApiConfig ,
1487+ task : "test task" ,
1488+ } )
1489+
1490+ // Mock saveClineMessages
1491+ const saveSpy = vi . spyOn ( cline as any , "saveClineMessages" ) . mockResolvedValue ( undefined )
1492+
1493+ // Set up messages with no api_req_started
1494+ cline . clineMessages = [
1495+ {
1496+ ts : Date . now ( ) ,
1497+ type : "say" ,
1498+ say : "text" ,
1499+ text : "Just a regular message" ,
1500+ } ,
1501+ ]
1502+
1503+ // Clear any calls from setup
1504+ saveSpy . mockClear ( )
1505+
1506+ // Call finish
1507+ await cline . finish ( )
1508+
1509+ // Verify saveClineMessages was NOT called
1510+ expect ( saveSpy ) . not . toHaveBeenCalled ( )
1511+
1512+ await cline . abortTask ( true )
1513+ await task . catch ( ( ) => { } )
1514+ } )
1515+ } )
13371516 } )
13381517} )
0 commit comments