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}