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