@@ -4,7 +4,7 @@ import { logger } from '../util/logger'
44import { Benchify } from 'benchify'
55import { env } from '@codebuff/internal/env'
66import { requestToolCall } from '../websockets/websocket-action'
7- import { createPatch } from 'diff'
7+ import { ParsedDiff , parsePatch } from 'diff'
88import { withRetry , withTimeout } from '@codebuff/common/util/promise'
99import { match , P } from 'ts-pattern'
1010import type {
@@ -454,8 +454,7 @@ async function applyBenchifyIfNeeded(
454454 } ,
455455) {
456456 // Early exit conditions - fail gracefully without blocking user edits
457- const client = getBenchifyClient ( )
458- if ( ! client || batchContext . intendedChanges . size === 0 ) {
457+ if ( batchContext . intendedChanges . size === 0 ) {
459458 return
460459 }
461460
@@ -499,15 +498,15 @@ async function applyBenchifyIfNeeded(
499498 logger . info (
500499 {
501500 benchifyResultCount : benchifyResult . length ,
502- resultFiles : benchifyResult . map ( ( r ) => r . path ) ,
501+ diffResults : benchifyResult . length ,
503502 agentStepId : options . agentStepId ,
504503 userInputId : options . userInputId ,
505504 } ,
506- `executeBatchStrReplaces: Benchify returned ${ benchifyResult . length } results, applying them` ,
505+ `executeBatchStrReplaces: Benchify returned ${ benchifyResult . length } diff results, applying them` ,
507506 )
508507
509508 // Apply results with individual error handling to prevent one failure from blocking others
510- await applyBenchifyResultsGracefully ( benchifyResult , {
509+ await applyBenchifyResultsGracefully ( filteredChanges , benchifyResult , {
511510 ws : batchContext . ws ,
512511 onResponseChunk : batchContext . onResponseChunk ,
513512 state : {
@@ -585,32 +584,34 @@ async function callBenchifyWithResilience(
585584 userInputId : string
586585 userId : string | undefined
587586 } ,
588- ) : Promise < { path : string ; contents : string } [ ] | null > {
587+ ) : Promise < ParsedDiff [ ] > {
589588 const client = getBenchifyClient ( )
590589 if ( ! client ) {
591- return null
590+ return [ ]
592591 }
593592
594593 return await withRetry (
595594 async ( ) => {
596- const response = await withTimeout (
595+ const diff_response = await withTimeout (
597596 client . runFixer ( editedFiles , {
598- fix_types : [ 'string_literals' ] ,
597+ fixes : [ 'parsing' ] ,
598+ mode : 'files' ,
599+ response_format : 'DIFF' ,
599600 } ) ,
600601 BENCHIFY_TIMEOUT_MS ,
601602 `Benchify call timed out after ${ BENCHIFY_TIMEOUT_MS } ms` ,
602603 )
603604
604605 // Validate response
605- if ( response && Array . isArray ( response ) ) {
606+ if ( diff_response ) {
606607 return validateBenchifyResponse (
607- response ,
608+ diff_response ,
608609 editedFiles ,
609610 context . agentStepId ,
610611 )
611612 }
612613
613- return null
614+ return [ ]
614615 } ,
615616 {
616617 maxRetries : 2 ,
@@ -634,42 +635,34 @@ async function callBenchifyWithResilience(
634635 * Validates Benchify API response using pattern matching
635636 */
636637function validateBenchifyResponse (
637- response : any [ ] ,
638+ response : string ,
638639 originalFiles : { path : string ; contents : string } [ ] ,
639640 agentStepId : string ,
640- ) : { path : string ; contents : string } [ ] {
641+ ) : ParsedDiff [ ] {
641642 const originalPaths = new Set ( originalFiles . map ( ( f ) => f . path ) )
642643
643- return response . flatMap ( ( result ) =>
644- match ( result )
645- . with ( { path : P . string , contents : P . string } , ( res ) => {
646- if ( ! originalPaths . has ( res . path ) ) {
644+ const patches = parsePatch ( response )
645+ return patches . flatMap ( ( patch ) =>
646+ match ( patch )
647+ . with ( { oldFileName : P . string } , ( res ) => {
648+ // drop prefix a/ adding by diff patch
649+ const actualFileName = res . oldFileName . replace ( 'a/' , '' )
650+ if ( ! originalPaths . has ( actualFileName ) ) {
647651 logger . warn (
648- { path : res . path , agentStepId } ,
652+ { path : actualFileName , agentStepId } ,
649653 'Benchify returned result for unexpected path' ,
650654 )
651655 return [ ]
652656 }
653- if ( res . contents . length > BENCHIFY_MAX_FILE_SIZE * 2 ) {
654- logger . warn (
655- {
656- path : res . path ,
657- size : res . contents . length ,
658- agentStepId,
659- } ,
660- 'Benchify result exceeds size limit' ,
661- )
662- return [ ]
663- }
664- return [ { path : res . path , contents : res . contents } ]
657+ return [ patch ]
665658 } )
666659 . otherwise ( ( ) => {
667660 logger . warn (
668661 {
669- result : JSON . stringify ( result ) . substring ( 0 , 100 ) ,
662+ result : JSON . stringify ( patch ) . substring ( 0 , 100 ) ,
670663 agentStepId,
671664 } ,
672- 'Invalid Benchify result structure ' ,
665+ 'Invalid Benchify patch ' ,
673666 )
674667 return [ ]
675668 } ) ,
@@ -707,7 +700,8 @@ function shouldRetryBenchifyError(error: Error): boolean {
707700 * Applies benchify results back to the file system with individual error handling
708701 */
709702async function applyBenchifyResultsGracefully (
710- benchifyFiles : { path : string ; contents : string } [ ] ,
703+ editedFiles : { path : string ; contents : string } [ ] ,
704+ benchifyDiffs : ParsedDiff [ ] ,
711705 context : {
712706 ws : WebSocket
713707 onResponseChunk : ( chunk : string | PrintModeEvent ) => void
@@ -719,9 +713,20 @@ async function applyBenchifyResultsGracefully(
719713 } ,
720714) {
721715 const results = await Promise . allSettled (
722- benchifyFiles . map ( ( benchifyFile ) =>
723- applyBenchifyResultSafely ( benchifyFile , context ) ,
724- ) ,
716+ editedFiles . map ( ( editedFile ) => {
717+ // again, we have to replace the a/ that the ParsedDiff introduced
718+ const diff = benchifyDiffs . find (
719+ ( v ) => v . oldFileName ?. replace ( 'a/' , '' ) == editedFile . path ,
720+ )
721+ if ( diff ) {
722+ applyBenchifyResultSafely ( editedFile , diff , context )
723+ } else {
724+ logger . warn (
725+ { file : editedFile . path } ,
726+ 'No Benchify diff found for file.' ,
727+ )
728+ }
729+ } ) ,
725730 )
726731
727732 // Log any failures but don't throw - individual file failures shouldn't block the batch
@@ -730,7 +735,7 @@ async function applyBenchifyResultsGracefully(
730735 logger . warn (
731736 {
732737 failureCount : failures . length ,
733- totalFiles : benchifyFiles . length ,
738+ totalFiles : editedFiles . length ,
734739 agentStepId : context . agentStepId ,
735740 } ,
736741 'Some Benchify results failed to apply' ,
@@ -743,6 +748,7 @@ async function applyBenchifyResultsGracefully(
743748 */
744749async function applyBenchifyResultSafely (
745750 benchifyFile : { path : string ; contents : string } ,
751+ benchifyDiff : ParsedDiff ,
746752 context : {
747753 ws : WebSocket
748754 onResponseChunk : ( chunk : string | PrintModeEvent ) => void
@@ -798,30 +804,12 @@ async function applyBenchifyResultSafely(
798804 return
799805 }
800806
801- // Skip if content is unchanged
802- if ( baseContent === benchifyFile . contents ) {
803- logger . debug (
804- { path : benchifyFile . path , agentStepId : context . agentStepId } ,
805- 'Benchify result identical to current content, skipping' ,
806- )
807- return
808- }
809-
810- // Generate a proper unified diff patch
811- const patch = createPatch (
812- benchifyFile . path ,
813- baseContent ,
814- benchifyFile . contents ,
815- '' ,
816- '' ,
817- )
818-
819807 // Apply with timeout to prevent hanging
820808 const toolCallResult = await withTimeout (
821809 requestToolCall ( context . ws , context . userInputId , 'str_replace' , {
822810 type : 'patch' ,
823811 path : benchifyFile . path ,
824- content : patch ,
812+ content : benchifyDiff ,
825813 } ) ,
826814 5000 ,
827815 'Benchify patch application timed out' ,
0 commit comments