refactor(core): remove provider module, migrate to LLMProvider, add client mode handling, improve serialization error handling, update workspace edition, and clean up conditionals and imports
This commit is contained in:
@@ -5,25 +5,27 @@ use crate::credentials::CredentialManager;
|
||||
use crate::encryption::{self, VaultHandle};
|
||||
use crate::formatting::MessageFormatter;
|
||||
use crate::input::InputBuffer;
|
||||
use crate::mcp::McpToolCall;
|
||||
use crate::mcp::client::McpClient;
|
||||
use crate::mcp::factory::McpClientFactory;
|
||||
use crate::mcp::permission::PermissionLayer;
|
||||
use crate::mcp::McpToolCall;
|
||||
use crate::mode::Mode;
|
||||
use crate::model::{DetailedModelInfo, ModelManager};
|
||||
use crate::provider::{ChatStream, Provider};
|
||||
use crate::providers::OllamaProvider;
|
||||
use crate::storage::{SessionMeta, StorageManager};
|
||||
use crate::types::{
|
||||
ChatParameters, ChatRequest, ChatResponse, Conversation, Message, ModelInfo, ToolCall,
|
||||
};
|
||||
use crate::ui::UiController;
|
||||
use crate::validation::{get_builtin_schemas, SchemaValidator};
|
||||
use crate::validation::{SchemaValidator, get_builtin_schemas};
|
||||
use crate::{ChatStream, Provider};
|
||||
use crate::{
|
||||
CodeExecTool, ResourcesDeleteTool, ResourcesGetTool, ResourcesListTool, ResourcesWriteTool,
|
||||
ToolRegistry, WebScrapeTool, WebSearchDetailedTool, WebSearchTool,
|
||||
};
|
||||
use crate::{Error, Result};
|
||||
use log::warn;
|
||||
use serde_json::Value;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -38,6 +40,51 @@ pub enum SessionOutcome {
|
||||
},
|
||||
}
|
||||
|
||||
fn extract_resource_content(value: &Value) -> Option<String> {
|
||||
match value {
|
||||
Value::Null => Some(String::new()),
|
||||
Value::Bool(flag) => Some(flag.to_string()),
|
||||
Value::Number(num) => Some(num.to_string()),
|
||||
Value::String(text) => Some(text.clone()),
|
||||
Value::Array(items) => {
|
||||
let mut segments = Vec::new();
|
||||
for item in items {
|
||||
if let Some(segment) = extract_resource_content(item)
|
||||
&& !segment.is_empty()
|
||||
{
|
||||
segments.push(segment);
|
||||
}
|
||||
}
|
||||
if segments.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(segments.join("\n"))
|
||||
}
|
||||
}
|
||||
Value::Object(map) => {
|
||||
const PREFERRED_FIELDS: [&str; 6] =
|
||||
["content", "contents", "text", "value", "body", "data"];
|
||||
for key in PREFERRED_FIELDS.iter() {
|
||||
if let Some(inner) = map.get(*key)
|
||||
&& let Some(text) = extract_resource_content(inner)
|
||||
&& !text.is_empty()
|
||||
{
|
||||
return Some(text);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(inner) = map.get("chunks")
|
||||
&& let Some(text) = extract_resource_content(inner)
|
||||
&& !text.is_empty()
|
||||
{
|
||||
return Some(text);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SessionController {
|
||||
provider: Arc<dyn Provider>,
|
||||
conversation: ConversationManager,
|
||||
@@ -55,6 +102,7 @@ pub struct SessionController {
|
||||
credential_manager: Option<Arc<CredentialManager>>,
|
||||
ui: Arc<dyn UiController>,
|
||||
enable_code_tools: bool,
|
||||
current_mode: Mode,
|
||||
}
|
||||
|
||||
async fn build_tools(
|
||||
@@ -228,6 +276,12 @@ impl SessionController {
|
||||
|
||||
drop(config_guard); // Release the lock before calling build_tools
|
||||
|
||||
let initial_mode = if enable_code_tools {
|
||||
Mode::Code
|
||||
} else {
|
||||
Mode::Chat
|
||||
};
|
||||
|
||||
let (tool_registry, schema_validator) = build_tools(
|
||||
config_arc.clone(),
|
||||
ui.clone(),
|
||||
@@ -247,8 +301,9 @@ impl SessionController {
|
||||
schema_validator.clone(),
|
||||
);
|
||||
let base_client = factory.create()?;
|
||||
let permission_client = PermissionLayer::new(base_client, Arc::new(guard.clone()));
|
||||
Arc::new(permission_client)
|
||||
let client = Arc::new(PermissionLayer::new(base_client, Arc::new(guard.clone())));
|
||||
client.set_mode(initial_mode).await?;
|
||||
client
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
@@ -268,6 +323,7 @@ impl SessionController {
|
||||
credential_manager,
|
||||
ui,
|
||||
enable_code_tools,
|
||||
current_mode: initial_mode,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -325,10 +381,10 @@ impl SessionController {
|
||||
.expect("Consent manager mutex poisoned");
|
||||
consent.grant_consent(tool_name, data_types, endpoints);
|
||||
|
||||
if let Some(vault) = &self.vault {
|
||||
if let Err(e) = consent.persist_to_vault(vault) {
|
||||
eprintln!("Warning: Failed to persist consent to vault: {}", e);
|
||||
}
|
||||
if let Some(vault) = &self.vault
|
||||
&& let Err(e) = consent.persist_to_vault(vault)
|
||||
{
|
||||
eprintln!("Warning: Failed to persist consent to vault: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,12 +403,11 @@ impl SessionController {
|
||||
consent.grant_consent_with_scope(tool_name, data_types, endpoints, scope);
|
||||
|
||||
// Only persist to vault for permanent consent
|
||||
if is_permanent {
|
||||
if let Some(vault) = &self.vault {
|
||||
if let Err(e) = consent.persist_to_vault(vault) {
|
||||
eprintln!("Warning: Failed to persist consent to vault: {}", e);
|
||||
}
|
||||
}
|
||||
if is_permanent
|
||||
&& let Some(vault) = &self.vault
|
||||
&& let Err(e) = consent.persist_to_vault(vault)
|
||||
{
|
||||
eprintln!("Warning: Failed to persist consent to vault: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,8 +544,13 @@ impl SessionController {
|
||||
};
|
||||
match self.mcp_client.call_tool(call).await {
|
||||
Ok(response) => {
|
||||
let content: String = serde_json::from_value(response.output)?;
|
||||
Ok(content)
|
||||
if let Some(text) = extract_resource_content(&response.output) {
|
||||
return Ok(text);
|
||||
}
|
||||
|
||||
let formatted = serde_json::to_string_pretty(&response.output)
|
||||
.unwrap_or_else(|_| response.output.to_string());
|
||||
Ok(formatted)
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("MCP file read failed ({}); falling back to local read", err);
|
||||
@@ -500,6 +560,48 @@ impl SessionController {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_file_with_tools(&self, path: &str) -> Result<String> {
|
||||
if !self.enable_code_tools {
|
||||
return Err(Error::InvalidInput(
|
||||
"Code tools are disabled in chat mode. Run `:mode code` to switch.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let call = McpToolCall {
|
||||
name: "resources/get".to_string(),
|
||||
arguments: serde_json::json!({ "path": path }),
|
||||
};
|
||||
|
||||
let response = self.mcp_client.call_tool(call).await?;
|
||||
if let Some(text) = extract_resource_content(&response.output) {
|
||||
Ok(text)
|
||||
} else {
|
||||
let formatted = serde_json::to_string_pretty(&response.output)
|
||||
.unwrap_or_else(|_| response.output.to_string());
|
||||
Ok(formatted)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn code_tools_enabled(&self) -> bool {
|
||||
self.enable_code_tools
|
||||
}
|
||||
|
||||
pub async fn set_code_tools_enabled(&mut self, enabled: bool) -> Result<()> {
|
||||
if self.enable_code_tools == enabled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.enable_code_tools = enabled;
|
||||
self.rebuild_tools().await
|
||||
}
|
||||
|
||||
pub async fn set_operating_mode(&mut self, mode: Mode) -> Result<()> {
|
||||
self.current_mode = mode;
|
||||
let enable_code_tools = matches!(mode, Mode::Code);
|
||||
self.set_code_tools_enabled(enable_code_tools).await?;
|
||||
self.mcp_client.set_mode(mode).await
|
||||
}
|
||||
|
||||
pub async fn list_dir(&self, path: &str) -> Result<Vec<String>> {
|
||||
let call = McpToolCall {
|
||||
name: "resources/list".to_string(),
|
||||
@@ -587,7 +689,9 @@ impl SessionController {
|
||||
);
|
||||
let base_client = factory.create()?;
|
||||
let permission_client = PermissionLayer::new(base_client, Arc::new(config.clone()));
|
||||
self.mcp_client = Arc::new(permission_client);
|
||||
let client = Arc::new(permission_client);
|
||||
client.set_mode(self.current_mode).await?;
|
||||
self.mcp_client = client;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -741,7 +845,7 @@ impl SessionController {
|
||||
if !streaming {
|
||||
const MAX_TOOL_ITERATIONS: usize = 5;
|
||||
for _iteration in 0..MAX_TOOL_ITERATIONS {
|
||||
match self.provider.chat(request.clone()).await {
|
||||
match self.provider.send_prompt(request.clone()).await {
|
||||
Ok(response) => {
|
||||
if response.message.has_tool_calls() {
|
||||
self.conversation.push_message(response.message.clone());
|
||||
@@ -786,7 +890,7 @@ impl SessionController {
|
||||
)));
|
||||
}
|
||||
|
||||
match self.provider.chat_stream(request).await {
|
||||
match self.provider.stream_prompt(request).await {
|
||||
Ok(stream) => {
|
||||
let response_id = self.conversation.start_streaming_response();
|
||||
Ok(SessionOutcome::Streaming {
|
||||
@@ -828,6 +932,11 @@ impl SessionController {
|
||||
.filter(|calls| !calls.is_empty())
|
||||
}
|
||||
|
||||
pub fn cancel_stream(&mut self, message_id: Uuid, notice: &str) -> Result<()> {
|
||||
self.conversation
|
||||
.cancel_stream(message_id, notice.to_string())
|
||||
}
|
||||
|
||||
pub async fn execute_streaming_tools(
|
||||
&mut self,
|
||||
_message_id: Uuid,
|
||||
|
||||
Reference in New Issue
Block a user