@@ -688,73 +688,75 @@ async def read_file(self, file_path: str, start_line: int = 1, end_line: int = N
688688 except Exception as e :
689689 return self .fail_response (f"Error reading file: { str (e )} " )
690690
691- async def _call_morph_api (self , file_content : str , code_edit : str , instructions : str , file_path : str ) -> Tuple [Optional [str ], Optional [str ]]:
691+ async def _call_relace_api (self , file_content : str , code_edit : str , instructions : str , file_path : str ) -> Tuple [Optional [str ], Optional [str ]]:
692692 """
693- Call Morph API to apply edits to file content.
693+ Call Relace API to apply edits to file content.
694694 Returns a tuple (new_content, error_message).
695695 On success, error_message is None.
696696 On failure, new_content is None.
697697 """
698+ import httpx
699+
698700 try :
699- morph_api_key = getattr (config , 'MORPH_API_KEY' , None ) or os .getenv ('MORPH_API_KEY' )
700- openrouter_key = getattr (config , 'OPENROUTER_API_KEY' , None ) or os .getenv ('OPENROUTER_API_KEY' )
701-
702- messages = [{
703- "role" : "user" ,
704- "content" : f"<instruction>{ instructions } </instruction>\n <code>{ file_content } </code>\n <update>{ code_edit } </update>"
705- }]
706-
707- response = None
708- if morph_api_key :
709- logger .debug ("Using direct Morph API for file editing." )
710- client = openai .AsyncOpenAI (
711- api_key = morph_api_key ,
712- base_url = "https://api.morphllm.com/v1"
713- )
714- response = await client .chat .completions .create (
715- model = "morph-v3-large" ,
716- messages = messages ,
717- temperature = 0.0 ,
718- timeout = 30.0
719- )
720- elif openrouter_key :
721- logger .debug ("Morph API key not set, falling back to OpenRouter for file editing via litellm." )
722- response = await litellm .acompletion (
723- model = "openrouter/morph/morph-v3-large" ,
724- messages = messages ,
725- api_key = openrouter_key ,
726- api_base = "https://openrouter.ai/api/v1" ,
727- temperature = 0.0 ,
728- timeout = 30.0
729- )
730- else :
731- error_msg = "No Morph or OpenRouter API key found, cannot perform AI edit."
732- logger .warning (error_msg )
733- return None , error_msg
734-
735- if response and response .choices and len (response .choices ) > 0 :
736- content = response .choices [0 ].message .content .strip ()
737-
738- # Extract code block if wrapped in markdown
739- if content .startswith ("```" ) and content .endswith ("```" ):
740- lines = content .split ('\n ' )
741- if len (lines ) > 2 :
742- content = '\n ' .join (lines [1 :- 1 ])
743-
744- return content , None
745- else :
746- error_msg = f"Invalid response from Morph/OpenRouter API: { response } "
747- logger .error (error_msg )
748- return None , error_msg
749-
701+ relace_api_key = getattr (config , 'RELACE_API_KEY' , None ) or os .getenv ('RELACE_API_KEY' )
702+
703+ if not relace_api_key :
704+ return None , "RELACE_API_KEY not configured"
705+
706+ # Use Direct REST API (more explicit response format)
707+ url = "https://instantapply.endpoint.relace.run/v1/code/apply"
708+
709+ headers = {
710+ "Authorization" : f"Bearer { relace_api_key } " ,
711+ "Content-Type" : "application/json"
712+ }
713+
714+ payload = {
715+ "initial_code" : file_content ,
716+ "edit_snippet" : code_edit ,
717+ "instruction" : instructions ,
718+ "model" : "auto" , # Routes to relace-apply-3
719+ "stream" : False ,
720+ "relace_metadata" : {
721+ "file_path" : file_path ,
722+ "source" : "cheatcode-agent"
723+ }
724+ }
725+
726+ async with httpx .AsyncClient (timeout = 60.0 ) as client :
727+ response = await client .post (url , headers = headers , json = payload )
728+
729+ if response .status_code == 200 :
730+ result = response .json ()
731+ merged_code = result .get ("mergedCode" )
732+ usage = result .get ("usage" , {})
733+
734+ logger .info (f"Relace API success - tokens: { usage .get ('total_tokens' , 'N/A' )} " )
735+
736+ if merged_code :
737+ return merged_code , None
738+ else :
739+ return None , "Relace API returned empty mergedCode"
740+
741+ elif response .status_code == 429 :
742+ return None , "Rate limit exceeded. Please wait and retry."
743+
744+ elif response .status_code == 413 :
745+ return None , f"File too large for Relace API (max 50MB). File: { file_path } "
746+
747+ elif response .status_code in [401 , 403 ]:
748+ return None , "Invalid or missing Relace API key"
749+
750+ else :
751+ error_body = response .text
752+ return None , f"Relace API error ({ response .status_code } ): { error_body [:500 ]} "
753+
754+ except httpx .TimeoutException :
755+ return None , f"Relace API timeout after 60s for file: { file_path } "
756+
750757 except Exception as e :
751- error_message = f"AI model call for file edit failed. Exception: { str (e )} "
752- # Try to get more details from the exception if it's an API error
753- if hasattr (e , 'response' ) and hasattr (e .response , 'text' ):
754- error_message += f"\n \n API Response Body:\n { e .response .text } "
755- elif hasattr (e , 'body' ): # litellm sometimes puts it in body
756- error_message += f"\n \n API Response Body:\n { e .body } "
757- logger .error (f"Error calling Morph/OpenRouter API: { error_message } " , exc_info = True )
758+ error_message = f"Relace API call failed: { str (e )} "
759+ logger .error (error_message , exc_info = True )
758760 return None , error_message
759761
760762 async def edit_file (self , target_file : str , instructions : str , code_edit : str ) -> ToolResult :
@@ -771,9 +773,9 @@ async def edit_file(self, target_file: str, instructions: str, code_edit: str) -
771773 # Read current content
772774 original_content = (await self .sandbox .fs .download_file (full_path )).decode ()
773775
774- # Try Morph AI editing first
775- logger .info (f"Attempting AI-powered edit for file '{ target_file } ' with instructions: { instructions [:100 ]} ..." )
776- new_content , error_message = await self ._call_morph_api (original_content , code_edit , instructions , target_file )
776+ # Use Relace fast-apply for intelligent code editing
777+ logger .info (f"Attempting Relace fast-apply edit for file '{ target_file } ' with instructions: { instructions [:100 ]} ..." )
778+ new_content , error_message = await self ._call_relace_api (original_content , code_edit , instructions , target_file )
777779
778780 if error_message :
779781 return self .fail_response (f"AI editing failed: { error_message } " )
0 commit comments