Skip to content

Commit a754a69

Browse files
committed
improve bash tool reliability, drop jupyter_ai_tools for now
1 parent 1ebccdb commit a754a69

File tree

2 files changed

+52
-4
lines changed

2 files changed

+52
-4
lines changed

packages/jupyter-ai/jupyter_ai/personas/base_persona.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ def get_tools(self, model_id: str) -> list[dict]:
477477
environment. These may include:
478478
479479
- The default set of tool functions in Jupyter AI, defined in the
480-
`jupyter_ai_tools` package.
480+
the default toolkit from `jupyter_ai.tools`.
481481
482482
- (TODO) Tools provided by MCP server configuration, if any.
483483

packages/jupyter-ai/jupyter_ai/tools/default_toolkit.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
from .models import Tool, Toolkit
2-
from jupyter_ai_tools.toolkits.code_execution import bash
3-
1+
import asyncio
42
import pathlib
3+
import shlex
4+
from typing import Optional
5+
6+
from .models import Tool, Toolkit
57

68

79
def read(file_path: str, offset: int, limit: int) -> str:
@@ -247,6 +249,52 @@ async def search_grep(pattern: str, include: str = "*") -> str:
247249
raise RuntimeError(f"Ripgrep search failed: {str(e)}") from e
248250

249251

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+
250298
DEFAULT_TOOLKIT = Toolkit(name="jupyter-ai-default-toolkit")
251299
DEFAULT_TOOLKIT.add_tool(Tool(callable=bash))
252300
DEFAULT_TOOLKIT.add_tool(Tool(callable=read))

0 commit comments

Comments
 (0)