Skip to main content

kaish_kernel/tools/
context.rs

1//! Execution context for tools.
2
3use std::path::PathBuf;
4use std::sync::Arc;
5
6use crate::ast::Value;
7use crate::backend::{KernelBackend, LocalBackend};
8use crate::dispatch::PipelinePosition;
9use crate::interpreter::Scope;
10use crate::scheduler::JobManager;
11use crate::tools::ToolRegistry;
12use crate::vfs::VfsRouter;
13
14use super::traits::ToolSchema;
15
16/// Output context determines how command output should be formatted.
17///
18/// Different contexts prefer different output formats:
19/// - **Interactive** — Pretty columns, colors, traditional tree (TTY/REPL)
20/// - **Piped** — Raw output for pipeline processing
21/// - **Model** — Token-efficient compact formats (MCP server / agent context)
22/// - **Script** — Non-interactive script execution
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub enum OutputContext {
25    /// Interactive TTY/REPL - use human-friendly format with colors.
26    #[default]
27    Interactive,
28    /// Output to another command - use raw output for pipes.
29    Piped,
30    /// MCP server / agent context - use token-efficient model format.
31    Model,
32    /// Non-interactive script - use raw output.
33    Script,
34}
35
36/// Execution context passed to tools.
37///
38/// Provides access to the backend (for file operations and tool dispatch),
39/// scope, and other kernel state.
40pub struct ExecContext {
41    /// Kernel backend for I/O operations.
42    ///
43    /// This is the preferred way to access filesystem operations.
44    /// Use `backend.read()`, `backend.write()`, etc.
45    pub backend: Arc<dyn KernelBackend>,
46    /// Variable scope.
47    pub scope: Scope,
48    /// Current working directory (VFS path).
49    pub cwd: PathBuf,
50    /// Previous working directory (for `cd -`).
51    pub prev_cwd: Option<PathBuf>,
52    /// Standard input for the tool (from pipeline).
53    pub stdin: Option<String>,
54    /// Structured data from pipeline (pre-parsed JSON from previous command).
55    /// Tools can check this before parsing stdin to avoid redundant JSON parsing.
56    pub stdin_data: Option<Value>,
57    /// Tool schemas for help command.
58    pub tool_schemas: Vec<ToolSchema>,
59    /// Tool registry reference (for tools that need to inspect available tools).
60    pub tools: Option<Arc<ToolRegistry>>,
61    /// Job manager for background jobs (optional).
62    pub job_manager: Option<Arc<JobManager>>,
63    /// Position of this command within a pipeline (for stdio decisions).
64    pub pipeline_position: PipelinePosition,
65}
66
67impl ExecContext {
68    /// Create a new execution context with a VFS (uses LocalBackend without tools).
69    ///
70    /// This constructor is for backward compatibility and tests that don't need tool dispatch.
71    /// For full tool support, use `with_vfs_and_tools`.
72    pub fn new(vfs: Arc<VfsRouter>) -> Self {
73        Self {
74            backend: Arc::new(LocalBackend::new(vfs)),
75            scope: Scope::new(),
76            cwd: PathBuf::from("/"),
77            prev_cwd: None,
78            stdin: None,
79            stdin_data: None,
80            tool_schemas: Vec::new(),
81            tools: None,
82            job_manager: None,
83            pipeline_position: PipelinePosition::Only,
84        }
85    }
86
87    /// Create a new execution context with VFS and tool registry.
88    ///
89    /// This is the preferred constructor for full kaish operation where
90    /// tools need to be dispatched through the backend.
91    pub fn with_vfs_and_tools(vfs: Arc<VfsRouter>, tools: Arc<ToolRegistry>) -> Self {
92        Self {
93            backend: Arc::new(LocalBackend::with_tools(vfs, tools.clone())),
94            scope: Scope::new(),
95            cwd: PathBuf::from("/"),
96            prev_cwd: None,
97            stdin: None,
98            stdin_data: None,
99            tool_schemas: Vec::new(),
100            tools: Some(tools),
101            job_manager: None,
102            pipeline_position: PipelinePosition::Only,
103        }
104    }
105
106    /// Create a new execution context with a custom backend.
107    pub fn with_backend(backend: Arc<dyn KernelBackend>) -> Self {
108        Self {
109            backend,
110            scope: Scope::new(),
111            cwd: PathBuf::from("/"),
112            prev_cwd: None,
113            stdin: None,
114            stdin_data: None,
115            tool_schemas: Vec::new(),
116            tools: None,
117            job_manager: None,
118            pipeline_position: PipelinePosition::Only,
119        }
120    }
121
122    /// Create a context with VFS, tools, and a specific scope.
123    pub fn with_vfs_tools_and_scope(vfs: Arc<VfsRouter>, tools: Arc<ToolRegistry>, scope: Scope) -> Self {
124        Self {
125            backend: Arc::new(LocalBackend::with_tools(vfs, tools.clone())),
126            scope,
127            cwd: PathBuf::from("/"),
128            prev_cwd: None,
129            stdin: None,
130            stdin_data: None,
131            tool_schemas: Vec::new(),
132            tools: Some(tools),
133            job_manager: None,
134            pipeline_position: PipelinePosition::Only,
135        }
136    }
137
138    /// Create a context with a specific scope (uses LocalBackend without tools).
139    ///
140    /// For tests that don't need tool dispatch. For full tool support,
141    /// use `with_vfs_tools_and_scope`.
142    pub fn with_scope(vfs: Arc<VfsRouter>, scope: Scope) -> Self {
143        Self {
144            backend: Arc::new(LocalBackend::new(vfs)),
145            scope,
146            cwd: PathBuf::from("/"),
147            prev_cwd: None,
148            stdin: None,
149            stdin_data: None,
150            tool_schemas: Vec::new(),
151            tools: None,
152            job_manager: None,
153            pipeline_position: PipelinePosition::Only,
154        }
155    }
156
157    /// Create a context with a custom backend and scope.
158    pub fn with_backend_and_scope(backend: Arc<dyn KernelBackend>, scope: Scope) -> Self {
159        Self {
160            backend,
161            scope,
162            cwd: PathBuf::from("/"),
163            prev_cwd: None,
164            stdin: None,
165            stdin_data: None,
166            tool_schemas: Vec::new(),
167            tools: None,
168            job_manager: None,
169            pipeline_position: PipelinePosition::Only,
170        }
171    }
172
173    /// Set the available tool schemas (for help command).
174    pub fn set_tool_schemas(&mut self, schemas: Vec<ToolSchema>) {
175        self.tool_schemas = schemas;
176    }
177
178    /// Set the tool registry reference.
179    pub fn set_tools(&mut self, tools: Arc<ToolRegistry>) {
180        self.tools = Some(tools);
181    }
182
183    /// Set the job manager for background job tracking.
184    pub fn set_job_manager(&mut self, manager: Arc<JobManager>) {
185        self.job_manager = Some(manager);
186    }
187
188    /// Set stdin for this execution.
189    pub fn set_stdin(&mut self, stdin: String) {
190        self.stdin = Some(stdin);
191    }
192
193    /// Get stdin, consuming it.
194    pub fn take_stdin(&mut self) -> Option<String> {
195        self.stdin.take()
196    }
197
198    /// Set both text stdin and structured data.
199    ///
200    /// Use this when passing output through a pipeline where the previous
201    /// command produced structured data (e.g., JSON from MCP tools).
202    pub fn set_stdin_with_data(&mut self, text: String, data: Option<Value>) {
203        self.stdin = Some(text);
204        self.stdin_data = data;
205    }
206
207    /// Take structured data if available, consuming it.
208    ///
209    /// Tools can use this to avoid re-parsing JSON that was already parsed
210    /// by a previous command in the pipeline.
211    pub fn take_stdin_data(&mut self) -> Option<Value> {
212        self.stdin_data.take()
213    }
214
215    /// Resolve a path relative to cwd.
216    pub fn resolve_path(&self, path: &str) -> PathBuf {
217        if path.starts_with('/') {
218            PathBuf::from(path)
219        } else {
220            self.cwd.join(path)
221        }
222    }
223
224    /// Change the current working directory.
225    ///
226    /// Saves the old directory for `cd -` support.
227    pub fn set_cwd(&mut self, path: PathBuf) {
228        self.prev_cwd = Some(self.cwd.clone());
229        self.cwd = path;
230    }
231
232    /// Get the previous working directory (for `cd -`).
233    pub fn get_prev_cwd(&self) -> Option<&PathBuf> {
234        self.prev_cwd.as_ref()
235    }
236}