@@ -3,7 +3,7 @@ use super::{AgentEvent, AgentLoop};
33use crate :: hooks:: {
44 ErrorType , GenerateEndEvent , GenerateStartEvent , HookEvent , TokenUsageInfo , ToolCallInfo ,
55} ;
6- use crate :: llm:: { LlmResponse , Message , ToolCall } ;
6+ use crate :: llm:: { LlmResponse , Message , ToolCall , ToolDefinition } ;
77use anyhow:: Context ;
88use std:: time:: Duration ;
99use tokio:: sync:: mpsc;
@@ -14,6 +14,16 @@ pub(super) struct LlmTurnOutput {
1414 pub ( super ) tool_calls : Vec < ToolCall > ,
1515}
1616
17+ struct LlmCallRequest < ' a > {
18+ turn : usize ,
19+ messages : & ' a [ Message ] ,
20+ system : Option < & ' a str > ,
21+ tools : & ' a [ ToolDefinition ] ,
22+ session_id : Option < & ' a str > ,
23+ event_tx : & ' a Option < mpsc:: Sender < AgentEvent > > ,
24+ cancel_token : & ' a tokio_util:: sync:: CancellationToken ,
25+ }
26+
1727impl AgentLoop {
1828 pub ( super ) async fn execute_llm_turn (
1929 & self ,
@@ -49,14 +59,15 @@ impl AgentLoop {
4959
5060 let llm_start = std:: time:: Instant :: now ( ) ;
5161 let response = self
52- . call_llm_with_circuit_breaker (
62+ . call_llm_with_circuit_breaker ( LlmCallRequest {
5363 turn,
54- & state. messages ,
55- augmented_system. as_deref ( ) ,
64+ messages : & state. messages ,
65+ system : augmented_system. as_deref ( ) ,
66+ tools : & selected_tools,
5667 session_id,
5768 event_tx,
5869 cancel_token,
59- )
70+ } )
6071 . await ?;
6172
6273 state. record_usage ( & response. usage ) ;
@@ -122,19 +133,14 @@ impl AgentLoop {
122133
123134 async fn call_llm_with_circuit_breaker (
124135 & self ,
125- turn : usize ,
126- messages : & [ Message ] ,
127- system : Option < & str > ,
128- session_id : Option < & str > ,
129- event_tx : & Option < mpsc:: Sender < AgentEvent > > ,
130- cancel_token : & tokio_util:: sync:: CancellationToken ,
136+ request : LlmCallRequest < ' _ > ,
131137 ) -> anyhow:: Result < LlmResponse > {
132138 // Consult the host's BudgetGuard once per turn (not per retry).
133139 // A `Deny` bails out before the LLM is touched; a `SoftLimit`
134140 // surfaces a BudgetThresholdHit event and proceeds.
135141 if let Some ( guard) = & self . config . budget_guard {
136- let sid = session_id. unwrap_or ( "" ) ;
137- let estimate = estimate_prompt_tokens ( messages, system) ;
142+ let sid = request . session_id . unwrap_or ( "" ) ;
143+ let estimate = estimate_prompt_tokens ( request . messages , request . system ) ;
138144 match guard. check_before_llm ( sid, estimate) . await {
139145 crate :: budget:: BudgetDecision :: Allow => { }
140146 crate :: budget:: BudgetDecision :: SoftLimit {
@@ -143,7 +149,7 @@ impl AgentLoop {
143149 limit,
144150 message,
145151 } => {
146- if let Some ( tx) = event_tx {
152+ if let Some ( tx) = request . event_tx {
147153 let _ = tx
148154 . send ( AgentEvent :: BudgetThresholdHit {
149155 resource,
@@ -156,7 +162,7 @@ impl AgentLoop {
156162 }
157163 }
158164 crate :: budget:: BudgetDecision :: Deny { resource, reason } => {
159- if let Some ( tx) = event_tx {
165+ if let Some ( tx) = request . event_tx {
160166 let _ = tx
161167 . send ( AgentEvent :: BudgetThresholdHit {
162168 resource : resource. clone ( ) ,
@@ -178,23 +184,31 @@ impl AgentLoop {
178184 loop {
179185 attempt += 1 ;
180186 let result = self
181- . call_llm ( messages, system, event_tx, cancel_token)
187+ . call_llm (
188+ request. messages ,
189+ request. system ,
190+ request. tools ,
191+ request. event_tx ,
192+ request. cancel_token ,
193+ )
182194 . await ;
183195 match result {
184196 Ok ( response) => {
185197 if let Some ( guard) = & self . config . budget_guard {
186198 guard
187- . record_after_llm ( session_id. unwrap_or ( "" ) , & response. usage )
199+ . record_after_llm ( request . session_id . unwrap_or ( "" ) , & response. usage )
188200 . await ;
189201 }
190202 return Ok ( response) ;
191203 }
192- Err ( error) if cancel_token. is_cancelled ( ) => {
204+ Err ( error) if request . cancel_token . is_cancelled ( ) => {
193205 anyhow:: bail!( error) ;
194206 }
195- Err ( error) if attempt < threshold && ( event_tx. is_none ( ) || attempt == 1 ) => {
207+ Err ( error)
208+ if attempt < threshold && ( request. event_tx . is_none ( ) || attempt == 1 ) =>
209+ {
196210 tracing:: warn!(
197- turn = turn,
211+ turn = request . turn,
198212 attempt = attempt,
199213 threshold = threshold,
200214 error = %error,
@@ -211,15 +225,15 @@ impl AgentLoop {
211225 } else {
212226 format ! ( "LLM call failed: {}" , error)
213227 } ;
214- tracing:: error!( turn = turn, attempt = attempt, "{}" , msg) ;
228+ tracing:: error!( turn = request . turn, attempt = attempt, "{}" , msg) ;
215229 self . fire_on_error (
216- session_id. unwrap_or ( "" ) ,
230+ request . session_id . unwrap_or ( "" ) ,
217231 ErrorType :: LlmFailure ,
218232 & msg,
219- serde_json:: json!( { "turn" : turn, "attempt" : attempt} ) ,
233+ serde_json:: json!( { "turn" : request . turn, "attempt" : attempt} ) ,
220234 )
221235 . await ;
222- self . emit_error ( event_tx, msg. clone ( ) ) . await ;
236+ self . emit_error ( request . event_tx , msg. clone ( ) ) . await ;
223237 anyhow:: bail!( msg) ;
224238 }
225239 }
@@ -365,23 +379,22 @@ impl AgentLoop {
365379 /// Streaming events (`TextDelta`, `ToolStart`) are forwarded to `event_tx`
366380 /// as they arrive. Non-streaming mode simply awaits the complete response.
367381 ///
368- /// Tool definitions are selected per turn by the centralized tool selector.
382+ /// Tool definitions are selected once per turn by the centralized tool selector.
369383 ///
370384 /// Returns `Err` on any LLM API failure. The circuit breaker in
371385 /// `execute_loop` wraps this call with retry logic for non-streaming mode.
372386 async fn call_llm (
373387 & self ,
374388 messages : & [ Message ] ,
375389 system : Option < & str > ,
390+ tools : & [ ToolDefinition ] ,
376391 event_tx : & Option < mpsc:: Sender < AgentEvent > > ,
377392 cancel_token : & tokio_util:: sync:: CancellationToken ,
378393 ) -> anyhow:: Result < LlmResponse > {
379- let tools = crate :: tools:: select_tools_for_messages ( & self . config . tools , messages) ;
380-
381394 if event_tx. is_some ( ) {
382395 let mut stream_rx = match self
383396 . llm_client
384- . complete_streaming ( messages, system, & tools, cancel_token. clone ( ) )
397+ . complete_streaming ( messages, system, tools, cancel_token. clone ( ) )
385398 . await
386399 {
387400 Ok ( rx) => rx,
@@ -396,7 +409,7 @@ impl AgentLoop {
396409 ) ;
397410 return self
398411 . llm_client
399- . complete ( messages, system, & tools)
412+ . complete ( messages, system, tools)
400413 . await
401414 . with_context ( || {
402415 format ! (
@@ -447,7 +460,7 @@ impl AgentLoop {
447460 final_response. context ( "Stream ended without final response" )
448461 } else {
449462 self . llm_client
450- . complete ( messages, system, & tools)
463+ . complete ( messages, system, tools)
451464 . await
452465 . context ( "LLM call failed" )
453466 }
0 commit comments