feat(tui): introduce comprehensive state management for TUI interface
- Add AppStateRest as the central state struct for managing TUI state. - Implement InputState for handling user input, autocomplete, and history. - Create MiscState to manage overlays, notifications, and editor state. - Introduce ScrollState for viewport scrolling functionality. - Develop TranscriptCache for efficient message rendering in the chat pane. - Implement SimpleAgent and SimpleWorkflowEngine for agent lifecycle management. - Add helper functions for managing effort levels and token counting. - Organize state-related modules for better maintainability and clarity.
This commit is contained in:
@@ -1,22 +1,74 @@
|
||||
//! TUI agent turn interface adapter — delegates execution to `zesdex_infrastructure::agent`.
|
||||
//! TUI agent turn interface adapter — resolves LLM provider configuration,
|
||||
//! builds the tool context, and spawns the agent turn on a background task.
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
use tracing::info;
|
||||
|
||||
use zesdex_domain::core::ChatMessage;
|
||||
use zesdex_domain::agent::AgentTurnParams;
|
||||
use zesdex_infrastructure::llm::provider::LlmClient;
|
||||
use zesdex_infrastructure::tools::executor::InfrastructureToolExecutor;
|
||||
use zesdex_infrastructure::tools::{all_tools, tool_defs, ToolCtx};
|
||||
use zesdex_application::agent::turn_service::AgentTurnServiceImpl;
|
||||
use zesdex_application::agent::AgentTurnService;
|
||||
|
||||
use crate::state::AppStateRest;
|
||||
|
||||
/// Spawn an agent turn on a background thread by delegating to `zesdex-infrastructure`.
|
||||
// ---------------------------------------------------------------------------
|
||||
// Provider resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Resolve the API key from settings or environment for the given provider.
|
||||
fn resolve_api_key(state: &AppStateRest, provider_name: &str) -> String {
|
||||
if let Some(key) = state.settings.api_keys.get(provider_name) {
|
||||
return key.clone();
|
||||
}
|
||||
if let Some(ref cfg) = state.app_config.providers.get(provider_name) {
|
||||
if let Some(ref default_key) = cfg.default_api_key {
|
||||
if !default_key.is_empty() {
|
||||
return default_key.clone();
|
||||
}
|
||||
}
|
||||
if let Some(ref env_name) = cfg.api_key_env {
|
||||
if let Ok(val) = std::env::var(env_name) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
String::new()
|
||||
}
|
||||
|
||||
/// Resolve the API base URL from the provider config.
|
||||
fn resolve_api_base(state: &AppStateRest, provider_name: &str) -> Option<String> {
|
||||
state
|
||||
.app_config
|
||||
.providers
|
||||
.get(provider_name)
|
||||
.map(|cfg| cfg.api_base.clone())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Turn spawning
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Spawn an agent turn on a background Tokio task.
|
||||
///
|
||||
/// Flow:
|
||||
/// 1. Compare-exchange the in-flight flag (no-op if already running).
|
||||
/// 2. Resolve provider (API key, model, base URL) from settings.
|
||||
/// 3. Build messages including the user's input text.
|
||||
/// 4. Construct `AgentTurnParams` with the turn-event queue and abort flag.
|
||||
/// 5. Create `LlmClient`, `ToolCtx`, and `InfrastructureToolExecutor`.
|
||||
/// 6. Assemble `AgentTurnServiceImpl` and spawn it via `tokio::spawn`.
|
||||
#[tracing::instrument(skip(state))]
|
||||
pub fn spawn_agent_turn(state: &mut AppStateRest, text: String) {
|
||||
// compare_exchange: only mark in-flight if not already running
|
||||
// Only one turn at a time — compare_exchange is lock-free
|
||||
if state
|
||||
.turn_in_flight_flag
|
||||
.compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
return; // already running
|
||||
return;
|
||||
}
|
||||
|
||||
let turn_events = state.turn_events.clone();
|
||||
@@ -25,29 +77,13 @@ pub fn spawn_agent_turn(state: &mut AppStateRest, text: String) {
|
||||
let session_dir = state.session_dir.clone();
|
||||
let workspace_roots = state.workspace_roots.clone();
|
||||
|
||||
// Resolve LLM provider configuration from settings
|
||||
// ── Resolve provider configuration ─────────────────────────────────
|
||||
let provider_name = &state.settings.provider;
|
||||
let provider_cfg = state.app_config.providers.get(provider_name).cloned();
|
||||
|
||||
let mut api_key = String::new();
|
||||
if let Some(key) = state.settings.api_keys.get(provider_name) {
|
||||
api_key = key.clone();
|
||||
} else if let Some(ref cfg) = provider_cfg {
|
||||
if let Some(ref default_key) = cfg.default_api_key {
|
||||
api_key = default_key.clone();
|
||||
}
|
||||
if api_key.is_empty() {
|
||||
if let Some(ref env_name) = cfg.api_key_env {
|
||||
if let Ok(val) = std::env::var(env_name) {
|
||||
api_key = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let api_key = resolve_api_key(state, provider_name);
|
||||
let model = state.settings.model.clone();
|
||||
let api_base = provider_cfg.map(|cfg| cfg.api_base.clone());
|
||||
let api_base = resolve_api_base(state, provider_name);
|
||||
|
||||
// ── Build message list ─────────────────────────────────────────────
|
||||
let mut messages: Vec<ChatMessage> = state
|
||||
.session_runtime
|
||||
.as_ref()
|
||||
@@ -59,8 +95,9 @@ pub fn spawn_agent_turn(state: &mut AppStateRest, text: String) {
|
||||
rt.messages = messages.clone();
|
||||
}
|
||||
|
||||
info!("delegating agent turn to infrastructure engine (model: {})", model);
|
||||
info!("Spawning agent turn (model: {model})");
|
||||
|
||||
// ── Assemble dependencies (composition root) ──────────────────────
|
||||
let params = AgentTurnParams {
|
||||
messages,
|
||||
session_dir: session_dir.clone(),
|
||||
@@ -73,32 +110,22 @@ pub fn spawn_agent_turn(state: &mut AppStateRest, text: String) {
|
||||
api_base: api_base.clone(),
|
||||
};
|
||||
|
||||
let client = std::sync::Arc::new(zesdex_infrastructure::llm::provider::LlmClient::new(
|
||||
api_key,
|
||||
model,
|
||||
api_base,
|
||||
));
|
||||
let client = std::sync::Arc::new(LlmClient::new(api_key, model, api_base));
|
||||
|
||||
let tool_ctx = zesdex_infrastructure::tools::ToolCtx::builder()
|
||||
let tool_ctx = ToolCtx::builder()
|
||||
.session_dir(session_dir)
|
||||
.workspaces(workspace_roots)
|
||||
.turn_events(turn_events)
|
||||
.build();
|
||||
|
||||
let tool_executor = std::sync::Arc::new(
|
||||
zesdex_infrastructure::tools::executor::InfrastructureToolExecutor::new(tool_ctx),
|
||||
);
|
||||
let tool_executor =
|
||||
std::sync::Arc::new(InfrastructureToolExecutor::new(tool_ctx));
|
||||
|
||||
let tools = zesdex_infrastructure::tools::all_tools();
|
||||
let defs = zesdex_infrastructure::tools::tool_defs(&tools);
|
||||
let tools = all_tools();
|
||||
let defs = tool_defs(&tools);
|
||||
|
||||
let turn_service = zesdex_application::agent::turn_service::AgentTurnServiceImpl::new(
|
||||
client,
|
||||
tool_executor,
|
||||
defs,
|
||||
);
|
||||
let turn_service = AgentTurnServiceImpl::new(client, tool_executor, defs);
|
||||
|
||||
use zesdex_application::agent::AgentTurnService;
|
||||
tokio::spawn(async move {
|
||||
let _ = turn_service.run_turn(params).await;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user