# Tool vs Provider: Different Lifecycles ## The Problem > "How can text plugin receive a tool call when it is not activated to main panel?" **Answer: Tool and Provider have different lifecycles.** ``` Plugin (EditorPlugin) ── lives as long as the app runs (or until plugin is unloaded) ├── Tool: open_file ── always available ├── Tool: save_file ── always available ├── Tool: find_and_replace ── always available │ └── Provider: EditorProvider ── created/destroyed per Main Panel instance ├── Instance A (showing main.rs) ├── Instance B (showing lib.rs) ← if we support split later └── destroyed when user closes the file ``` ## Key Insight **Tool is registered at Plugin level, not Provider level.** ```rust pub trait Plugin { fn bootstrap(registrar: &mut dyn Registrar); } pub trait Registrar { // Tool lives with the Plugin fn register_tool(&mut self, desc: ToolDesc, handler: Box); // Provider is a factory — created on demand when panel needs it fn register_provider(&mut self, desc: ProviderDesc, factory: ProviderFactory); } ``` ## How It Works ### Plugin State (Shared) ```rust pub struct EditorPlugin { // Plugin-level state: all open documents documents: HashMap, config: EditorConfig, } impl Plugin for EditorPlugin { fn bootstrap(registrar: &mut dyn Registrar) { let plugin = Arc::new(Mutex::new(EditorPlugin::new())); // Register tools — these live with the plugin, not any provider instance registrar.register_tool( ToolDesc { name: "open_file", ... }, Box::new(OpenFileTool { plugin: plugin.clone() }), ); registrar.register_tool( ToolDesc { name: "find_and_replace", ... }, Box::new(FindReplaceTool { plugin: plugin.clone() }), ); // Register provider factory registrar.register_provider( ProviderDesc { id: "editor", panel: PanelType::Main, ... }, Box::new(move |_| Box::new(EditorProvider::new(plugin.clone()))), ); } } ``` ### Tool Execution (Always Available) ```rust pub struct OpenFileTool { plugin: Arc>, } impl Tool for OpenFileTool { fn execute(&self, input: ToolInput) -> ToolOutput { let path = input.args["path"].as_str().unwrap(); let content = std::fs::read_to_string(path).unwrap(); // 1. Store in plugin-level state self.plugin.lock().documents.insert(path.into(), Document::new(content)); // 2. Request UI to open this file in Main Panel // This goes through a UI action, not the tool system runtime.ui_actions().open_file(path); ToolOutput::ok("File opened") } } ``` ### Provider Render (Reads from Shared State) ```rust pub struct EditorProvider { plugin: Arc>, current_file: PathBuf, } impl PanelProvider for EditorProvider { fn render(&mut self, area: Rect, buf: &mut Buffer) { let plugin = self.plugin.lock(); let doc = plugin.documents.get(&self.current_file).unwrap(); // render document content } } ``` ## Lifecycle Comparison | | Tool | Provider | |---|---|---| | **Registered by** | Plugin | Plugin | | **Created when** | Plugin bootstrap | Panel needs to display content | | **Destroyed when** | Plugin unload | Panel switches away (unless keep_alive) | | **Can be called when** | Always | Only when panel is active (render/event) | | **Has state** | Yes, Plugin-level shared state | Yes, instance-level state (cursor, scroll) | ## Agent Opens a File: Full Flow ``` Agent: "Open src/main.rs" → LLM decides to call tool "open_file" → Tool Registry finds "open_file" (registered by EditorPlugin) → OpenFileTool.execute(path: "src/main.rs") → Read file from disk → Store in EditorPlugin.documents → Call ui_actions.open_file("src/main.rs") → UI Framework: Main Panel provider = "editor" → UI Framework: EditorProvider.current_file = "src/main.rs" → EditorProvider.init() → EditorProvider.render() — reads from shared state → Tool returns "File opened" to Agent → Agent displays "I've opened src/main.rs for you" ``` ## Why This Matters **Without this separation:** - Agent calls `open_file` → Editor Provider not active → crash or no-op - Tool must wait for Provider to be created → Agent workflow broken **With this separation:** - Tool operates on Plugin-level state, independent of UI - UI switches to Provider after Tool completes - Provider renders from shared state ## Design Rule > **Plugin = shared state + tools + provider factory.** > **Tool = operates on shared state, always callable.** > **Provider = renders shared state into a panel, only active when visible.**