Refactor scrolling methods in ScrollState to accept an amount parameter
- Updated `scroll_up` and `scroll_down` methods to take an `amount` parameter for more flexible scrolling. - Removed the `AgentMode` enum and related methods from the types module to simplify state management. - Modified `AppStateRest` to remove the `mode` field and adjusted related logic. - Enhanced `run_subagent` to build tool definitions and handle API key resolution from configuration. - Updated command parsing to reflect changes in login handling. - Removed onboarding overlays and related logic from input handling and rendering. - Improved status bar to reflect connection status and agent readiness. - Adjusted workflow panel rendering to simplify phase status display. - Refactored edit log initialization to load from disk if available. - Updated settings structure to use a HashMap for API keys. - Enhanced error handling in LlmClient for authentication issues.
This commit is contained in:
@@ -6,7 +6,7 @@ use crate::app::mode::ModeKind;
|
||||
use crate::app::review::{should_trigger_review, trigger_review};
|
||||
use crate::app::state::rest::{AppStateRest, ChatMessageDisplay};
|
||||
use crate::app::state::runtime::TurnEvent;
|
||||
use crate::app::state::types::{AgentMode, Origin, Overlay, Toast, ToastKind};
|
||||
use crate::app::state::types::{Origin, Overlay, Toast, ToastKind};
|
||||
use crate::dto::chat::message::{ChatMessage, Role};
|
||||
|
||||
const MAX_TOOL_ONLY_TURNS: usize = 6;
|
||||
@@ -60,6 +60,7 @@ pub enum Action {
|
||||
command: String,
|
||||
},
|
||||
ModelList,
|
||||
AbortTurn,
|
||||
}
|
||||
|
||||
pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
||||
@@ -77,8 +78,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
||||
ModeKind::Help => Overlay::Help,
|
||||
ModeKind::Settings => Overlay::Settings,
|
||||
ModeKind::QuitConfirm => Overlay::QuitConfirm,
|
||||
ModeKind::Onboard => Overlay::Onboard,
|
||||
ModeKind::OnboardProvider => Overlay::OnboardProvider,
|
||||
|
||||
ModeKind::KeyInput => Overlay::KeyInput,
|
||||
ModeKind::Editor => Overlay::Editor,
|
||||
ModeKind::Effort => Overlay::Effort,
|
||||
@@ -132,12 +132,11 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
||||
state.dirty = true;
|
||||
}
|
||||
Action::ScrollUp => {
|
||||
state.scroll.scroll_up();
|
||||
state.scroll.scroll_up(5);
|
||||
state.dirty = true;
|
||||
}
|
||||
Action::ScrollDown => {
|
||||
let total = state.transcript_cache.messages.len();
|
||||
state.scroll.scroll_down(total);
|
||||
state.scroll.scroll_down(5);
|
||||
state.dirty = true;
|
||||
}
|
||||
Action::OpenOverlay(overlay) => {
|
||||
@@ -419,6 +418,10 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
||||
state.dirty = true;
|
||||
}
|
||||
}
|
||||
Action::AbortTurn => {
|
||||
state.abort_flag.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
state.push_toast(Toast::new(ToastKind::Warning, "Aborting generation...".to_string()));
|
||||
}
|
||||
Action::LessonAccept { name } => {
|
||||
if let Some(ref rt) = state.session_runtime {
|
||||
let _ = crate::app::review::resolve_pending_lesson(
|
||||
@@ -459,7 +462,7 @@ fn spawn_turn(state: &AppStateRest) {
|
||||
if messages.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut api_key = state.settings.api_key.clone().unwrap_or_default();
|
||||
let mut api_key = state.settings.api_keys.get(&state.settings.provider).cloned().unwrap_or_default();
|
||||
let model = state.settings.model.clone();
|
||||
let base_url = state.app_config.providers.get(&state.settings.provider)
|
||||
.map(|p| p.api_base.clone());
|
||||
@@ -482,12 +485,14 @@ fn spawn_turn(state: &AppStateRest) {
|
||||
tools.extend(state.mcp_manager.as_tools());
|
||||
let tool_defs = crate::tool::tool_defs(&tools);
|
||||
let ctx = state.tool_ctx();
|
||||
let mode = state.mode;
|
||||
|
||||
let edit_session_dir = state.session_dir.clone();
|
||||
let session_id = state.session_id.clone();
|
||||
let turn_events = state.turn_events.clone();
|
||||
let in_flight_flag = state.turn_in_flight.clone();
|
||||
let workspace_roots: Vec<std::path::PathBuf> = ctx.workspaces.clone();
|
||||
let abort_flag = state.abort_flag.clone();
|
||||
abort_flag.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
*in_flight_flag.lock().unwrap() = true;
|
||||
|
||||
@@ -502,13 +507,14 @@ fn spawn_turn(state: &AppStateRest) {
|
||||
tdefs: tool_defs,
|
||||
tools,
|
||||
ctx,
|
||||
mode,
|
||||
|
||||
workspace_roots,
|
||||
edit_log_session_dir: edit_session_dir,
|
||||
session_id,
|
||||
db,
|
||||
temperature,
|
||||
max_tokens,
|
||||
abort_flag,
|
||||
};
|
||||
let result = run_agent_turn(tc, &messages, &events_q);
|
||||
if let Err(e) = result {
|
||||
@@ -527,13 +533,14 @@ struct TurnCtx {
|
||||
tdefs: Vec<crate::dto::provider::request::ToolDef>,
|
||||
tools: Vec<Box<dyn crate::tool::Tool>>,
|
||||
ctx: crate::tool::ToolCtx,
|
||||
mode: AgentMode,
|
||||
|
||||
workspace_roots: Vec<std::path::PathBuf>,
|
||||
edit_log_session_dir: std::path::PathBuf,
|
||||
session_id: String,
|
||||
db: Option<std::sync::Arc<std::sync::Mutex<rusqlite::Connection>>>,
|
||||
temperature: f32,
|
||||
max_tokens: u32,
|
||||
abort_flag: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
||||
}
|
||||
|
||||
fn archive_message(db: &Option<std::sync::Arc<std::sync::Mutex<rusqlite::Connection>>>, session_id: &str, msg: &ChatMessage) {
|
||||
@@ -589,47 +596,57 @@ fn run_agent_turn(
|
||||
};
|
||||
|
||||
let mut stream_started = false;
|
||||
let (response, usage) = match tc.client.chat_with_tools_streaming(
|
||||
let mut usage = None;
|
||||
let result = tc.client.chat_with_tools_streaming(
|
||||
&wire_msgs,
|
||||
Some(tc.tdefs.clone()),
|
||||
Some(tc.temperature),
|
||||
Some(tc.max_tokens),
|
||||
|event| match event {
|
||||
crate::app::runtime::stream::StreamEvent::Token(tok) => {
|
||||
if let Ok(mut q) = events_q.lock() {
|
||||
if !stream_started {
|
||||
q.push_back(TurnEvent::StreamStart);
|
||||
stream_started = true;
|
||||
|event| -> bool {
|
||||
if tc.abort_flag.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
return false;
|
||||
}
|
||||
if let Ok(mut q) = events_q.lock() {
|
||||
match event {
|
||||
crate::app::runtime::stream::StreamEvent::Token(tok) => {
|
||||
if !stream_started {
|
||||
q.push_back(TurnEvent::StreamStart);
|
||||
stream_started = true;
|
||||
}
|
||||
q.push_back(TurnEvent::StreamToken(tok.clone()));
|
||||
}
|
||||
q.push_back(TurnEvent::StreamToken(tok.clone()));
|
||||
crate::app::runtime::stream::StreamEvent::Usage { prompt_tokens, completion_tokens, .. } => {
|
||||
usage = Some((*prompt_tokens, *completion_tokens));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
crate::app::runtime::stream::StreamEvent::Usage { prompt_tokens, completion_tokens, .. } => {
|
||||
if let Ok(mut q) = events_q.lock() {
|
||||
q.push_back(TurnEvent::Usage {
|
||||
tokens_in: *prompt_tokens,
|
||||
tokens_out: *completion_tokens,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
true
|
||||
},
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(_stream_err) => {
|
||||
// Provider doesn't support streaming — fall back to non-streaming
|
||||
);
|
||||
|
||||
let (response, final_usage) = match result {
|
||||
Ok((msg, u)) => (msg, u.or(usage)),
|
||||
Err(e) => {
|
||||
if tc.abort_flag.load(std::sync::atomic::Ordering::SeqCst) || e.to_string().contains("aborted") {
|
||||
if let Ok(mut q) = events_q.lock() {
|
||||
q.push_back(TurnEvent::Error("Generation aborted by user".to_string()));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let (msg, usage_fb) = tc.client.chat_with_tools_non_streaming(
|
||||
&wire_msgs, Some(tc.tdefs.clone()),
|
||||
)?;
|
||||
if let Some((tok_in, tok_out)) = usage_fb {
|
||||
if let Ok(mut q) = events_q.lock() {
|
||||
q.push_back(TurnEvent::Usage { tokens_in: tok_in, tokens_out: tok_out });
|
||||
}
|
||||
}
|
||||
(msg, usage_fb)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((tok_in, tok_out)) = final_usage {
|
||||
if let Ok(mut q) = events_q.lock() {
|
||||
q.push_back(TurnEvent::Usage { tokens_in: tok_in, tokens_out: tok_out });
|
||||
}
|
||||
}
|
||||
|
||||
let has_tool_calls = response.tool_calls.is_some()
|
||||
&& response.tool_calls.as_ref().is_some_and(|tc| !tc.is_empty());
|
||||
|
||||
@@ -640,6 +657,12 @@ fn run_agent_turn(
|
||||
archive_message(&tc.db, &tc.session_id, &response);
|
||||
msgs.push(response);
|
||||
for tool_call in tool_calls {
|
||||
if tc.abort_flag.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
if let Ok(mut q) = events_q.lock() {
|
||||
q.push_back(TurnEvent::Error("Turn aborted by user".to_string()));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let tool_name = tool_call.function.name.clone();
|
||||
let args = crate::dto::chat::tool::sanitize_tool_arguments(
|
||||
&tool_call.function.arguments,
|
||||
@@ -650,7 +673,7 @@ fn run_agent_turn(
|
||||
let verdict = crate::app::harness::Harness::gate_tool_call(
|
||||
&tool_name,
|
||||
&args,
|
||||
&tc.mode,
|
||||
|
||||
&ws_roots,
|
||||
);
|
||||
|
||||
@@ -671,7 +694,7 @@ fn run_agent_turn(
|
||||
},
|
||||
Verdict::Block(reason) => (format!("Blocked: {}", reason), true, false),
|
||||
Verdict::Escalate => (
|
||||
"Tool requires approval. Switch to Auto mode or provide explicit approval."
|
||||
"Tool requires approval. Provide explicit approval."
|
||||
.to_string(),
|
||||
true,
|
||||
false,
|
||||
|
||||
Reference in New Issue
Block a user