Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/apps/cli/src/agent/agentic_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ pub async fn init_agentic_system() -> Result<AgenticSystem> {
tool_registry,
tool_state_manager,
None,
None,
));

let stream_processor = Arc::new(execution::StreamProcessor::new(event_queue.clone()));
Expand Down
11 changes: 6 additions & 5 deletions src/apps/desktop/src/api/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
use crate::api::app_state::AppState;
use crate::api::dto::WorkspaceInfoDto;
use bitfun_core::infrastructure::{
file_watcher, BatchedFileSearchProgressSink, FileOperationOptions, FileSearchResult,
FileSearchResultGroup, FileTreeNode, SearchMatchType,
BatchedFileSearchProgressSink, FileOperationOptions, FileSearchResult, FileSearchResultGroup,
FileTreeNode, SearchMatchType,
};
use bitfun_core::service::file_watch;
use bitfun_core::service::remote_ssh::workspace_state::is_remote_path;
use bitfun_core::service::remote_ssh::{get_remote_workspace_manager, RemoteWorkspaceEntry};
use bitfun_core::service::workspace::{
Expand Down Expand Up @@ -2925,15 +2926,15 @@ pub async fn report_ide_control_result(request: IdeControlResultRequest) -> Resu

#[tauri::command]
pub async fn start_file_watch(path: String, recursive: Option<bool>) -> Result<(), String> {
file_watcher::start_file_watch(path, recursive).await
file_watch::start_file_watch(path, recursive).await
}

#[tauri::command]
pub async fn stop_file_watch(path: String) -> Result<(), String> {
file_watcher::stop_file_watch(path).await
file_watch::stop_file_watch(path).await
}

#[tauri::command]
pub async fn get_watched_paths() -> Result<Vec<String>, String> {
file_watcher::get_watched_paths().await
file_watch::get_watched_paths().await
}
11 changes: 1 addition & 10 deletions src/apps/desktop/src/api/tool_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ use log::error;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;

use crate::api::context_upload_api::create_image_context_provider;
use bitfun_core::agentic::{
tools::framework::ToolUseContext,
tools::{get_all_tools, get_readonly_tools},
Expand Down Expand Up @@ -90,20 +88,13 @@ fn build_tool_context(workspace_path: Option<&str>) -> ToolUseContext {

ToolUseContext {
tool_call_id: None,
message_id: None,
agent_type: None,
session_id: None,
dialog_turn_id: None,
workspace: normalized_workspace_path
.map(|path| WorkspaceBinding::new(None, PathBuf::from(path))),
safe_mode: Some(false),
abort_controller: None,
read_file_timestamps: HashMap::new(),
options: None,
response_state: None,
image_context_provider: Some(Arc::new(create_image_context_provider())),
custom_data: HashMap::new(),
computer_use_host: None,
subagent_parent_info: None,
cancellation_token: None,
workspace_services: None,
}
Expand Down
27 changes: 2 additions & 25 deletions src/apps/desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use std::sync::{
#[cfg(target_os = "macos")]
use tauri::Emitter;
use tauri::Manager;
use tauri_plugin_log::{RotationStrategy, TimezoneStrategy};

// Re-export API
pub use api::*;
Expand Down Expand Up @@ -148,27 +147,7 @@ pub async fn run() {
setup_panic_hook();

let run_result = tauri::Builder::default()
.plugin(
tauri_plugin_log::Builder::new()
.level(log::LevelFilter::Trace)
.level_for("ignore", log::LevelFilter::Off)
.level_for("ignore::walk", log::LevelFilter::Off)
.level_for("globset", log::LevelFilter::Off)
.level_for("tracing", log::LevelFilter::Off)
.level_for("opentelemetry_sdk", log::LevelFilter::Off)
.level_for("opentelemetry-otlp", log::LevelFilter::Off)
.level_for("notify", log::LevelFilter::Off)
.level_for("hyper_util", log::LevelFilter::Info)
.level_for("h2", log::LevelFilter::Info)
.level_for("portable_pty", log::LevelFilter::Info)
.level_for("russh", log::LevelFilter::Info)
.targets(log_targets)
.rotation_strategy(RotationStrategy::KeepSome(2)) // 1 active + 2 backups
.max_file_size(10 * 1024 * 1024)
.timezone_strategy(TimezoneStrategy::UseLocal)
.clear_format()
.build(),
)
.plugin(logging::build_log_plugin(log_targets))
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
Expand Down Expand Up @@ -771,7 +750,6 @@ async fn init_agentic_system() -> anyhow::Result<(

let tool_registry = tools::registry::get_global_tool_registry();
let tool_state_manager = Arc::new(tools::pipeline::ToolStateManager::new(event_queue.clone()));
let image_context_provider = Arc::new(api::context_upload_api::create_image_context_provider());

let computer_use_host: ComputerUseHostRef =
Arc::new(computer_use::DesktopComputerUseHost::new());
Expand All @@ -780,7 +758,6 @@ async fn init_agentic_system() -> anyhow::Result<(
let tool_pipeline = Arc::new(tools::pipeline::ToolPipeline::new(
tool_registry,
tool_state_manager,
Some(image_context_provider),
Some(computer_use_host),
));

Expand Down Expand Up @@ -958,7 +935,7 @@ fn init_services(app_handle: tauri::AppHandle, default_log_level: log::LevelFilt

service::snapshot::initialize_snapshot_event_emitter(emitter.clone());

infrastructure::initialize_file_watcher(emitter.clone());
bitfun_core::service::initialize_file_watch_service(emitter.clone());

if let Err(e) = workspace_identity_watch_service
.set_event_emitter(emitter.clone())
Expand Down
25 changes: 24 additions & 1 deletion src/apps/desktop/src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use std::sync::{
OnceLock,
};
use std::thread;
use tauri_plugin_log::{fern, Target, TargetKind};
use tauri::{plugin::TauriPlugin, Runtime};
use tauri_plugin_log::{fern, RotationStrategy, Target, TargetKind, TimezoneStrategy};

const SESSION_DIR_PATTERN: &str = r"^\d{8}T\d{6}$";
const MAX_LOG_SESSIONS: usize = 10;
Expand Down Expand Up @@ -274,6 +275,28 @@ pub fn build_log_targets(config: &LogConfig) -> Vec<Target> {
targets
}

pub fn build_log_plugin<R: Runtime>(log_targets: Vec<Target>) -> TauriPlugin<R> {
tauri_plugin_log::Builder::new()
.level(log::LevelFilter::Trace)
.level_for("ignore", log::LevelFilter::Off)
.level_for("ignore::walk", log::LevelFilter::Off)
.level_for("globset", log::LevelFilter::Off)
.level_for("tracing", log::LevelFilter::Off)
.level_for("opentelemetry_sdk", log::LevelFilter::Off)
.level_for("opentelemetry-otlp", log::LevelFilter::Off)
.level_for("notify", log::LevelFilter::Off)
.level_for("hyper_util", log::LevelFilter::Info)
.level_for("h2", log::LevelFilter::Info)
.level_for("portable_pty", log::LevelFilter::Info)
.level_for("russh", log::LevelFilter::Info)
.targets(log_targets)
.rotation_strategy(RotationStrategy::KeepSome(2)) // 1 active + 2 backups
.max_file_size(10 * 1024 * 1024)
.timezone_strategy(TimezoneStrategy::UseLocal)
.clear_format()
.build()
}

fn format_log_plain(
out: fern::FormatCallback,
message: &std::fmt::Arguments,
Expand Down
1 change: 0 additions & 1 deletion src/apps/server/src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ pub async fn initialize(workspace: Option<String>) -> anyhow::Result<Arc<ServerA
let tool_pipeline = Arc::new(tools::pipeline::ToolPipeline::new(
tool_registry.clone(),
tool_state_manager,
None, // no image context provider in server mode for now
None,
));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! System prompts module providing main dialogue and agent dialogue prompts
use crate::agentic::util::get_formatted_files_list;
use crate::infrastructure::try_get_path_manager_arc;
use crate::service::agent_memory::build_workspace_agent_memory_prompt;
use crate::service::ai_memory::AIMemoryManager;
use crate::service::ai_rules::get_global_ai_rules_service;
use crate::service::bootstrap::build_workspace_persona_prompt;
use crate::service::config::get_app_language_code;
use crate::service::filesystem::get_formatted_directory_listing;
use crate::service::config::global::GlobalConfigManager;
use crate::service::project_context::ProjectContextService;
use crate::util::errors::{BitFunError, BitFunResult};
Expand Down Expand Up @@ -166,20 +166,22 @@ impl PromptBuilder {
return project_layout;
}

let (hit_limit, formatted_files_list) = get_formatted_files_list(
let formatted_listing = get_formatted_directory_listing(
&self.context.workspace_path,
self.file_tree_max_entries,
None,
)
.unwrap_or_else(|e| (false, format!("Error listing directory: {}", e)));
.unwrap_or_else(|e| crate::service::filesystem::FormattedDirectoryListing {
reached_limit: false,
text: format!("Error listing directory: {}", e),
});
let mut project_layout = "# Workspace Layout\n<project_layout>\n".to_string();
if hit_limit {
if formatted_listing.reached_limit {
project_layout.push_str(&format!("Below is a snapshot of the current workspace's file structure (showing up to {} entries).\n\n", self.file_tree_max_entries));
} else {
project_layout
.push_str("Below is a snapshot of the current workspace's file structure.\n\n");
}
project_layout.push_str(&formatted_files_list);
project_layout.push_str(&formatted_listing.text);
project_layout.push_str("\n</project_layout>\n\n");
project_layout
}
Expand All @@ -202,7 +204,6 @@ impl PromptBuilder {
let result = format!(
r#"# Project Context
The following are project documentation that describe the project's architecture, conventions, and guidelines, etc.
These files are maintained by the user and should NOT be modified unless explicitly requested.

{}

Expand Down
23 changes: 1 addition & 22 deletions src/crates/core/src/agentic/execution/execution_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use crate::agentic::image_analysis::{
ImageLimits,
};
use crate::agentic::session::{CompressionTailPolicy, ContextCompressor, SessionManager};
use crate::agentic::tools::framework::ToolOptions;
use crate::agentic::tools::{get_all_registered_tools, SubagentParentInfo};
use crate::agentic::util::build_remote_workspace_layout_preview;
use crate::agentic::{WorkspaceBackend, WorkspaceBinding};
Expand Down Expand Up @@ -1480,32 +1479,12 @@ impl ExecutionEngine {
);
let description_context = crate::agentic::tools::framework::ToolUseContext {
tool_call_id: None,
message_id: None,
agent_type: Some(agent_type.to_string()),
session_id: None,
dialog_turn_id: None,
workspace: workspace.cloned(),
safe_mode: None,
abort_controller: None,
read_file_timestamps: Default::default(),
options: Some(ToolOptions {
commands: vec![],
tools: vec![],
verbose: None,
slow_and_capable_model: None,
safe_mode: None,
fork_number: None,
message_log_name: None,
max_thinking_tokens: None,
is_koding_request: None,
koding_context: None,
is_custom_command: None,
custom_data: Some(tool_opts_custom),
}),
response_state: None,
image_context_provider: None,
custom_data: tool_opts_custom,
computer_use_host: None,
subagent_parent_info: None,
cancellation_token: None,
workspace_services: None,
};
Expand Down
52 changes: 11 additions & 41 deletions src/crates/core/src/agentic/tools/framework.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
//! Tool framework - Tool interface definition and execution context
use super::image_context::ImageContextProviderRef;
use super::pipeline::SubagentParentInfo;
use crate::agentic::workspace::WorkspaceServices;
use crate::agentic::WorkspaceBinding;
use crate::util::errors::BitFunResult;
Expand All @@ -16,21 +14,14 @@ use tokio_util::sync::CancellationToken;
#[derive(Debug, Clone)]
pub struct ToolUseContext {
pub tool_call_id: Option<String>,
pub message_id: Option<String>,
pub agent_type: Option<String>,
pub session_id: Option<String>,
pub dialog_turn_id: Option<String>,
pub workspace: Option<WorkspaceBinding>,
pub safe_mode: Option<bool>,
pub abort_controller: Option<String>,
pub read_file_timestamps: HashMap<String, u64>,
pub options: Option<ToolOptions>,
pub response_state: Option<ResponseState>,
/// Image context provider (dependency injection)
pub image_context_provider: Option<ImageContextProviderRef>,
/// Extended context data passed from execution layer to tools.
pub custom_data: HashMap<String, Value>,
/// Desktop automation (Computer use); only set in BitFun desktop.
pub computer_use_host: Option<crate::agentic::tools::computer_use_host::ComputerUseHostRef>,
pub subagent_parent_info: Option<SubagentParentInfo>,
// Cancel tool execution more timely, especially for tools like TaskTool that need to run for a long time
pub cancellation_token: Option<CancellationToken>,
/// Workspace I/O services (filesystem + shell) — use these instead of
Expand Down Expand Up @@ -61,10 +52,8 @@ impl ToolUseContext {
/// Whether the session primary model accepts image inputs (from tool-definition / pipeline context).
/// Defaults to **true** when unset (e.g. API listings without model metadata).
pub fn primary_model_supports_image_understanding(&self) -> bool {
self.options
.as_ref()
.and_then(|o| o.custom_data.as_ref())
.and_then(|m| m.get("primary_model_supports_image_understanding"))
self.custom_data
.get("primary_model_supports_image_understanding")
.and_then(|v| v.as_bool())
.unwrap_or(true)
}
Expand All @@ -90,31 +79,6 @@ impl ToolUseContext {
}
}

/// Tool options
#[derive(Debug, Clone)]
pub struct ToolOptions {
pub commands: Vec<Value>,
pub tools: Vec<String>,
pub verbose: Option<bool>,
pub slow_and_capable_model: Option<String>,
pub safe_mode: Option<bool>,
pub fork_number: Option<u32>,
pub message_log_name: Option<String>,
pub max_thinking_tokens: Option<u32>,
pub is_koding_request: Option<bool>,
pub koding_context: Option<String>,
pub is_custom_command: Option<bool>,
/// Extended data fields, for passing extra context information
pub custom_data: Option<HashMap<String, Value>>,
}

/// Response state - for model state management like GPT-5
#[derive(Debug, Clone)]
pub struct ResponseState {
pub previous_response_id: Option<String>,
pub conversation_id: Option<String>,
}

/// Validation result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationResult {
Expand Down Expand Up @@ -302,13 +266,19 @@ pub trait Tool: Send + Sync {
format!("{} completed", self.name())
}

/// Call tool - return async generator
/// Execute the tool's concrete business logic.
/// Implementors should put the actual tool behavior here and assume
/// [`call`] will wrap it with cross-cutting concerns such as cancellation.
async fn call_impl(
&self,
input: &Value,
context: &ToolUseContext,
) -> BitFunResult<Vec<ToolResult>>;

/// Unified tool entry point.
/// This method owns shared framework behavior and delegates the actual
/// execution to [`call_impl`], so most tools should override `call_impl`
/// instead of overriding this method directly.
async fn call(&self, input: &Value, context: &ToolUseContext) -> BitFunResult<Vec<ToolResult>> {
if let Some(cancellation_token) = context.cancellation_token.as_ref() {
tokio::select! {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,8 @@ The **primary model cannot consume images** in tool results — **do not** use *
}

fn primary_api_format(ctx: &ToolUseContext) -> String {
ctx.options
.as_ref()
.and_then(|o| o.custom_data.as_ref())
.and_then(|m| m.get("primary_model_provider"))
ctx.custom_data
.get("primary_model_provider")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_lowercase()
Expand Down
Loading
Loading