|
1 |
| -from .models import Tool, Toolkit |
2 |
| -from jupyter_ai_tools.toolkits.code_execution import bash |
3 |
| - |
| 1 | +import asyncio |
4 | 2 | import pathlib
|
| 3 | +import shlex |
| 4 | +from typing import Optional |
| 5 | + |
| 6 | +from .models import Tool, Toolkit |
5 | 7 |
|
6 | 8 |
|
7 | 9 | def read(file_path: str, offset: int, limit: int) -> str:
|
@@ -247,6 +249,52 @@ async def search_grep(pattern: str, include: str = "*") -> str:
|
247 | 249 | raise RuntimeError(f"Ripgrep search failed: {str(e)}") from e
|
248 | 250 |
|
249 | 251 |
|
| 252 | +async def bash(command: str, timeout: Optional[int] = None) -> str: |
| 253 | + """Executes a bash command and returns the result |
| 254 | +
|
| 255 | + Args: |
| 256 | + command: The bash command to execute |
| 257 | + timeout: Optional timeout in seconds |
| 258 | +
|
| 259 | + Returns: |
| 260 | + The command output (stdout and stderr combined) |
| 261 | + """ |
| 262 | + # coerce `timeout` to the correct type. sometimes LLMs pass this as a string |
| 263 | + if isinstance(timeout, str): |
| 264 | + timeout = int(timeout) |
| 265 | + |
| 266 | + proc = await asyncio.create_subprocess_exec( |
| 267 | + *shlex.split(command), |
| 268 | + stdout=asyncio.subprocess.PIPE, |
| 269 | + stderr=asyncio.subprocess.PIPE, |
| 270 | + ) |
| 271 | + |
| 272 | + try: |
| 273 | + stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout) |
| 274 | + stdout = stdout.decode("utf-8") |
| 275 | + stderr = stderr.decode("utf-8") |
| 276 | + |
| 277 | + if proc.returncode != 0: |
| 278 | + info = f"Command returned non-zero exit code {proc.returncode}. This usually indicates an error." |
| 279 | + info += "\n\n" + fr"Original command: {command}" |
| 280 | + if not (stdout or stderr): |
| 281 | + info += "\n\nNo further information was given in stdout or stderr." |
| 282 | + return info |
| 283 | + if stdout: |
| 284 | + info += f"stdout:\n\n```\n{stdout}\n```\n\n" |
| 285 | + if stderr: |
| 286 | + info += f"stderr:\n\n```\n{stderr}\n```\n\n" |
| 287 | + return info |
| 288 | + |
| 289 | + if stdout: |
| 290 | + return stdout |
| 291 | + return "Command executed successfully with exit code 0. No stdout/stderr was returned." |
| 292 | + |
| 293 | + except asyncio.TimeoutError: |
| 294 | + proc.kill() |
| 295 | + return f"Command timed out after {timeout} seconds" |
| 296 | + |
| 297 | + |
250 | 298 | DEFAULT_TOOLKIT = Toolkit(name="jupyter-ai-default-toolkit")
|
251 | 299 | DEFAULT_TOOLKIT.add_tool(Tool(callable=bash))
|
252 | 300 | DEFAULT_TOOLKIT.add_tool(Tool(callable=read))
|
|
0 commit comments