4848)
4949from cecli .helpers .profiler import TokenProfiler
5050from cecli .history import ChatSummary
51+ from cecli .hooks import HookIntegration
5152from cecli .io import ConfirmGroup , InputOutput
5253from cecli .linter import Linter
5354from cecli .llm import litellm
@@ -1590,6 +1591,10 @@ async def preproc_user_input(self, inp):
15901591 async def run_one (self , user_message , preproc ):
15911592 self .init_before_message ()
15921593
1594+ if not await HookIntegration .call_start_hooks (self ):
1595+ self .io .tool_warning ("Execution stopped by start hook" )
1596+ return
1597+
15931598 if preproc :
15941599 message = await self .preproc_user_input (user_message )
15951600 else :
@@ -1637,6 +1642,10 @@ async def run_one(self, user_message, preproc):
16371642
16381643 await self .auto_save_session (force = True )
16391644
1645+ if not await HookIntegration .call_end_hooks (self ):
1646+ self .io .tool_warning ("Execution stopped by end hook" )
1647+ return
1648+
16401649 def _is_url_allowed (self , url ):
16411650 allowed_domains = self .security_config .get ("allowed-domains" )
16421651 if not allowed_domains :
@@ -2274,7 +2283,7 @@ async def send_message(self, inp):
22742283
22752284 self .io .tool_output ()
22762285 self .show_usage_report ()
2277- self .add_assistant_reply_to_cur_messages ()
2286+ await self .add_assistant_reply_to_cur_messages ()
22782287
22792288 if exhausted :
22802289 cur_messages = ConversationManager .get_messages_dict (MessageTag .CUR )
@@ -2596,6 +2605,13 @@ async def _exec_server_tools(server, tool_calls_list):
25962605 new_tool_call = copy_tool_call (tool_call )
25972606 new_tool_call .function .arguments = json .dumps (args )
25982607
2608+ if not await HookIntegration .call_pre_tool_hooks (
2609+ self , new_tool_call .function .name , args
2610+ ):
2611+ self .io .tool_warning ("Tool call skipped by pre-tool call hook" )
2612+ all_results_content .append ("Tool Request Aborted." )
2613+ continue
2614+
25992615 call_result = await experimental_mcp_client .call_openai_tool (
26002616 session = session ,
26012617 openai_tool = new_tool_call ,
@@ -2628,6 +2644,16 @@ async def _exec_server_tools(server, tool_calls_list):
26282644 content_parts .append (item .text )
26292645
26302646 result_text = "" .join (content_parts )
2647+
2648+ if not await HookIntegration .call_post_tool_hooks (
2649+ self , new_tool_call .function .name , args , result_text
2650+ ):
2651+ self .io .tool_warning (
2652+ "Tool call output skipped by post-tool call hook"
2653+ )
2654+ all_results_content .append ("Tool Response Redacted." )
2655+ continue
2656+
26312657 all_results_content .append (result_text )
26322658
26332659 tool_responses .append (
@@ -2803,7 +2829,7 @@ def __del__(self):
28032829 """Cleanup when the Coder object is destroyed."""
28042830 self .ok_to_warm_cache = False
28052831
2806- def add_assistant_reply_to_cur_messages (self ):
2832+ async def add_assistant_reply_to_cur_messages (self ):
28072833 """
28082834 Add the assistant's reply to `cur_messages`.
28092835 Handles model-specific quirks, like Deepseek which requires `content`
@@ -2845,6 +2871,10 @@ def add_assistant_reply_to_cur_messages(self):
28452871 or msg .get ("tool_calls" , None )
28462872 or msg .get ("function_call" , None )
28472873 ):
2874+ if not await HookIntegration .call_end_message_hooks (self , str (msg )):
2875+ self .io .tool_warning ("Execution stopped by end message hook" )
2876+ return
2877+
28482878 ConversationManager .add_message (
28492879 message_dict = msg ,
28502880 tag = MessageTag .CUR ,
@@ -2972,7 +3002,7 @@ async def send(self, messages, model=None, functions=None, tools=None):
29723002 async for chunk in self .show_send_output_stream (completion ):
29733003 yield chunk
29743004 else :
2975- self .show_send_output (completion )
3005+ await self .show_send_output (completion )
29763006
29773007 response , func_err , content_err = self .consolidate_chunks ()
29783008
@@ -3002,7 +3032,7 @@ async def send(self, messages, model=None, functions=None, tools=None):
30023032 if args :
30033033 self .io .ai_output (json .dumps (args , indent = 4 ))
30043034
3005- def show_send_output (self , completion ):
3035+ async def show_send_output (self , completion ):
30063036 if self .verbose :
30073037 print (completion )
30083038
@@ -3018,6 +3048,10 @@ def show_send_output(self, completion):
30183048
30193049 response , func_err , content_err = self .consolidate_chunks ()
30203050
3051+ if not await HookIntegration .call_on_message_hooks (self , self .partial_response_content ):
3052+ self .io .tool_warning ("Execution stopped by on message hook" )
3053+ return
3054+
30213055 resp_hash = dict (
30223056 function_call = str (self .partial_response_function_call ),
30233057 content = self .partial_response_content ,
@@ -3183,6 +3217,10 @@ async def show_send_output_stream(self, completion):
31833217 # The Part Doing the Heavy Lifting Now
31843218 self .consolidate_chunks ()
31853219
3220+ if not await HookIntegration .call_on_message_hooks (self , self .partial_response_content ):
3221+ self .io .tool_warning ("Execution stopped by on message hook" )
3222+ return
3223+
31863224 if not received_content and len (self .partial_response_tool_calls ) == 0 :
31873225 self .io .tool_warning ("Empty response received from LLM. Check your provider account?" )
31883226
0 commit comments