feat(tui): enhance agent turn handling by grouping parameters and improving message management

This commit is contained in:
asepharyana
2026-07-20 15:21:23 +07:00
parent 9bfb95d795
commit 07b217cf4a
6 changed files with 62 additions and 78 deletions
+1
View File
@@ -153,6 +153,7 @@ pub fn apply_action(state: &mut crate::state::AppStateRest, action: Action) {
if let Some(ref mut rt) = state.session_runtime {
rt.usage.tokens_in = rt.usage.tokens_in.saturating_add(tokens_in);
rt.usage.tokens_out = rt.usage.tokens_out.saturating_add(tokens_out);
rt.usage.api_calls = rt.usage.api_calls.saturating_add(1);
}
}
zesdex_infrastructure::TurnEvent::Error(msg) => {
+1 -1
View File
@@ -975,7 +975,7 @@ impl AppStateRest {
abort_flag: Arc::new(AtomicBool::new(false)),
dir_cache: Arc::new(tokio::sync::RwLock::new(DirCache::new())),
mention_index: MentionIndex::new(),
session_runtime: None,
session_runtime: Some(zesdex_infrastructure::SessionRuntime::new(session_dir.to_path_buf())),
workflow_engine: SimpleWorkflowEngine::new(),
transcript_cache: TranscriptCache::new(200),
scroll: ScrollState::new(),
+47 -43
View File
@@ -70,56 +70,62 @@ pub fn spawn_agent_turn(state: &mut AppStateRest, text: String) {
info!("spawning agent turn with {} messages (model: {})", messages.len(), model);
std::thread::spawn(move || {
run_turn(
&mut messages,
&session_dir,
&workspace_roots,
&turn_events,
&in_flight,
&abort,
let params = TurnParams {
messages: &mut messages,
session_dir: &session_dir,
workspace_roots: &workspace_roots,
turn_events: &turn_events,
in_flight: &in_flight,
abort: &abort,
api_key,
model,
api_base,
);
};
run_turn(params);
});
}
/// The core agent turn — LLM call → tool execution → repeat.
fn run_turn(
messages: &mut Vec<ChatMessage>,
session_dir: &Path,
workspace_roots: &[PathBuf],
turn_events: &Arc<Mutex<VecDeque<TurnEvent>>>,
in_flight: &Arc<AtomicBool>,
abort: &Arc<AtomicBool>,
/// Parameters for a turn, grouped to avoid too-many-arguments lint.
struct TurnParams<'a> {
messages: &'a mut Vec<ChatMessage>,
session_dir: &'a Path,
workspace_roots: &'a [PathBuf],
turn_events: &'a Arc<Mutex<VecDeque<TurnEvent>>>,
in_flight: &'a Arc<AtomicBool>,
abort: &'a Arc<AtomicBool>,
api_key: String,
model: String,
api_base: Option<String>,
) {
let client = LlmClient::new(api_key, model, api_base);
}
/// The core agent turn — LLM call → tool execution → repeat.
fn run_turn(params: TurnParams) {
let client = LlmClient::new(params.api_key, params.model, params.api_base);
let tools = all_tools();
let defs = tool_defs(&tools);
// Prepend system message
let sys_msg = ChatMessage::system(
"You are Zesdex, an AI coding assistant. You have access to various tools via native function calling to help the user. \
For complex problems, use `seq_think` to reason step-by-step. \
For any non-trivial tasks, you MUST prioritize creating a structured plan (using `plan_enter`) and a list of TODOs (using `todowrite`) BEFORE executing any other tools or modifying files. \
For large multi-step operations or delegating tasks, you MUST prioritize using `workflow_run` (to run a yaml workflow script) or `hive_mind` (to orchestrate multiple agents) to complete the task efficiently. \
Use other tools when necessary. If a tool returns an error, fix the issue before retrying. \
Respond conversationally, concisely, and helpfully."
.to_string(),
);
messages.insert(0, sys_msg);
params.messages.insert(0, sys_msg);
let tool_ctx = ToolCtx::builder()
.session_dir(session_dir.to_path_buf())
.workspaces(workspace_roots.to_vec())
.session_dir(params.session_dir.to_path_buf())
.workspaces(params.workspace_roots.to_vec())
.turn_events(Arc::clone(params.turn_events))
.build();
for iteration in 0..50 {
if abort.load(Ordering::SeqCst) {
abort.store(false, Ordering::SeqCst);
push_event(turn_events, TurnEvent::SystemNote {
if params.abort.load(Ordering::SeqCst) {
params.abort.store(false, Ordering::SeqCst);
push_event(params.turn_events, TurnEvent::SystemNote {
kind: "info".into(),
message: "Turn aborted by user".into(),
});
@@ -129,29 +135,29 @@ fn run_turn(
debug!("agent turn iteration {iteration}");
// Stream the LLM response
push_event(turn_events, TurnEvent::StreamStart);
push_event(params.turn_events, TurnEvent::StreamStart);
let result = client.chat_with_tools_streaming(
messages,
params.messages,
Some(defs.clone()),
Some(0.7),
Some(4096),
|event| {
if abort.load(Ordering::SeqCst) {
if params.abort.load(Ordering::SeqCst) {
return false;
}
match event {
zesdex_domain::core::StreamEvent::Token(s) => {
push_event(turn_events, TurnEvent::StreamToken(s.clone()));
push_event(params.turn_events, TurnEvent::StreamToken(s.clone()));
}
zesdex_domain::core::StreamEvent::Reasoning(s) => {
push_event(turn_events, TurnEvent::StreamReasoning(s.clone()));
push_event(params.turn_events, TurnEvent::StreamReasoning(s.clone()));
}
_ => {}
}
true
},
Some(&abort),
Some(params.abort),
);
match result {
@@ -159,21 +165,21 @@ fn run_turn(
let content = assistant_msg.content.clone().unwrap_or_default();
let tool_calls = assistant_msg.tool_calls.clone().unwrap_or_default();
push_event(turn_events, TurnEvent::StreamDone(assistant_msg.clone()));
push_event(params.turn_events, TurnEvent::StreamDone(assistant_msg.clone()));
if let Some((tokens_in, tokens_out)) = usage {
push_event(turn_events, TurnEvent::Usage {
push_event(params.turn_events, TurnEvent::Usage {
tokens_in,
tokens_out,
});
}
if tool_calls.is_empty() {
messages.push(ChatMessage::assistant(Some(content)));
params.messages.push(ChatMessage::assistant(Some(content)));
break;
}
messages.push(assistant_msg);
params.messages.push(assistant_msg);
for tc in &tool_calls {
let name = &tc.function.name;
@@ -192,7 +198,7 @@ fn run_turn(
let is_error = output.starts_with("Error:");
push_event(turn_events, TurnEvent::ToolResult {
push_event(params.turn_events, TurnEvent::ToolResult {
tool_call_id: tc.id.clone(),
tool_name: name.clone(),
output: output.clone(),
@@ -200,12 +206,12 @@ fn run_turn(
path: None,
});
messages.push(ChatMessage::tool(tc.id.clone(), output.clone()));
params.messages.push(ChatMessage::tool(tc.id.clone(), output.clone()));
}
}
Err(e) => {
warn!("LLM call failed: {e}");
push_event(turn_events, TurnEvent::Error(format!("LLM error: {e}")));
push_event(params.turn_events, TurnEvent::Error(format!("LLM error: {e}")));
break;
}
}
@@ -213,11 +219,9 @@ fn run_turn(
// Propagate accumulated messages back to session_runtime so the next
// turn starts with the full history (assistant replies + tool results).
// TurnEvent::Compacted already exists on the enum and is handled in
// action.rs to write back to state.session_runtime.messages.
push_event(turn_events, TurnEvent::Compacted(messages.clone()));
push_event(turn_events, TurnEvent::Done);
mark_done(in_flight);
push_event(params.turn_events, TurnEvent::Compacted(params.messages.clone()));
push_event(params.turn_events, TurnEvent::Done);
mark_done(params.in_flight);
}
fn push_event(queue: &Arc<Mutex<VecDeque<TurnEvent>>>, event: TurnEvent) {