Skip to content

Commit 8f05c53

Browse files
authored
Merge pull request #15 from 3coins/remove-tool-models
2 parents d26745a + dffc69e commit 8f05c53

File tree

8 files changed

+127
-88
lines changed

8 files changed

+127
-88
lines changed

README.md

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,41 @@ ______________________________________________________________________
1010

1111
This extension provides runtime-discoverable tools compatible with OpenAI-style function calling or MCP tool schemas. These tools can be invoked by agents to:
1212

13-
### 🧠 YNotebook Tools
14-
15-
- `read_cell`: Return the full content of a cell by index
16-
- `read_notebook`: Return all cells as a JSON-formatted list
17-
- `add_cell`: Insert a blank cell at a specific index
18-
- `delete_cell`: Remove a cell and return its contents
19-
- `write_to_cell`: Overwrite the content of a cell with new source
20-
- `get_max_cell_index`: Return the last valid cell index
21-
22-
### 🌀 Git Tools
23-
24-
- `git_clone`: Clone a Git repo into a given path
25-
- `git_status`: Get the working tree status
13+
### 📁 File System Tools (`fs_toolkit`)
14+
15+
- `read`: Read file contents from the filesystem
16+
- `edit`: Edit file contents with search and replace functionality
17+
- `write`: Write content to a file
18+
- `search_and_replace`: Search and replace text patterns in files
19+
- `glob`: Find files matching a glob pattern
20+
- `grep`: Search for text patterns within file contents
21+
- `ls`: List directory contents
22+
23+
### 🧠 Notebook Tools (`nb_toolkit`)
24+
25+
- `read_notebook`: Read entire notebook contents as markdown
26+
- `read_cell`: Read a specific notebook cell by index
27+
- `add_cell`: Add a new cell to a notebook
28+
- `insert_cell`: Insert a cell at a specific index in the notebook
29+
- `delete_cell`: Remove a cell from the notebook
30+
- `edit_cell`: Modify a cell's content
31+
- `get_cell_id_from_index`: Get cell ID from its index position
32+
- `create_notebook`: Create a new Jupyter notebook
33+
34+
### 🌀 Git Tools (`git_toolkit`)
35+
36+
- `git_clone`: Clone a Git repository to a specified path
37+
- `git_status`: Get the current working tree status
2638
- `git_log`: View recent commit history
27-
- `git_add`: Stage files (individually or all)
39+
- `git_pull`: Pull changes from remote repository
40+
- `git_push`: Push local changes to remote branch
2841
- `git_commit`: Commit staged changes with a message
29-
- `git_push`: Push local changes to a remote branch
30-
- `git_pull`: Pull remote updates
31-
- `git_get_repo_root_from_notebookpath`: Find the Git root from a notebook path
42+
- `git_add`: Stage files for commit (individually or all)
43+
- `git_get_repo_root`: Get the root directory of the Git repository
44+
45+
### ⚙️ Code Execution Tools (`exec_toolkit`)
46+
47+
- `bash`: Execute bash commands in the system shell
3248

3349
These tools are ideal for agents that assist users with code editing, version control, or dynamic notebook interaction.
3450

jupyter_ai_tools/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
from .toolkits.code_execution import toolkit as exec_toolkit
2+
from .toolkits.file_system import toolkit as fs_toolkit
3+
from .toolkits.git import toolkit as git_toolkit
4+
from .toolkits.notebook import toolkit as nb_toolkit
5+
16
__version__ = "0.2.1"
27

8+
__all__ = [
9+
"fs_toolkit",
10+
"exec_toolkit",
11+
"git_toolkit",
12+
"nb_toolkit",
13+
]
14+
315

416
def _jupyter_server_extension_points():
517
return [{"module": "jupyter_ai_tools"}]

jupyter_ai_tools/toolkits/code_execution.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import shlex
55
from typing import Optional
66

7-
from jupyter_ai.tools.models import Tool, Toolkit
8-
97

108
async def bash(command: str, timeout: Optional[int] = None) -> str:
119
"""Executes a bash command and returns the result
@@ -40,8 +38,6 @@ async def bash(command: str, timeout: Optional[int] = None) -> str:
4038
return f"Command timed out after {timeout} seconds"
4139

4240

43-
toolkit = Toolkit(
44-
name="code_execution_toolkit",
45-
description="Tools to execute code in different environments.",
46-
)
47-
toolkit.add_tool(Tool(callable=bash, execute=True))
41+
toolkit = [
42+
bash,
43+
]

jupyter_ai_tools/toolkits/file_system.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
import os
77
from typing import List, Optional
88

9-
from jupyter_ai.tools.models import Tool, Toolkit
10-
119
from ..utils import normalize_filepath
1210

1311

@@ -356,14 +354,12 @@ async def ls(path: str, ignore: Optional[List[str]] = None) -> str:
356354
return f"Error: Failed to list directory: {str(e)}"
357355

358356

359-
toolkit = Toolkit(
360-
name="file_system_toolkit",
361-
description="Tools to do search, list, read, write and edit operations on files.",
362-
)
363-
toolkit.add_tool(Tool(callable=read, read=True))
364-
toolkit.add_tool(Tool(callable=edit, read=True, write=True))
365-
toolkit.add_tool(Tool(callable=write, write=True))
366-
toolkit.add_tool(Tool(callable=search_and_replace, read=True, write=True))
367-
toolkit.add_tool(Tool(callable=glob, read=True))
368-
toolkit.add_tool(Tool(callable=grep, read=True))
369-
toolkit.add_tool(Tool(callable=ls, read=True))
357+
toolkit = [
358+
read,
359+
edit,
360+
write,
361+
search_and_replace,
362+
glob,
363+
grep,
364+
ls,
365+
]

jupyter_ai_tools/toolkits/git.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import json
22
import os
33

4-
from jupyter_ai.tools.models import Tool, Toolkit
54
from jupyterlab_git.git import Git
65

76
from ..utils import normalize_filepath
@@ -175,15 +174,13 @@ async def git_get_repo_root(path: str) -> str:
175174
return f"❌ Not inside a Git repo. {res.get('message', '')}"
176175

177176

178-
toolkit = Toolkit(
179-
name="git_toolkit",
180-
description="Tools for working with Git repositories.",
181-
)
182-
toolkit.add_tool(Tool(callable=git_clone, execute=True))
183-
toolkit.add_tool(Tool(callable=git_status, read=True))
184-
toolkit.add_tool(Tool(callable=git_log, read=True))
185-
toolkit.add_tool(Tool(callable=git_pull, execute=True))
186-
toolkit.add_tool(Tool(callable=git_push, execute=True))
187-
toolkit.add_tool(Tool(callable=git_commit, execute=True))
188-
toolkit.add_tool(Tool(callable=git_add, execute=True))
189-
toolkit.add_tool(Tool(callable=git_get_repo_root, read=True))
177+
toolkit = [
178+
git_clone,
179+
git_status,
180+
git_log,
181+
git_pull,
182+
git_push,
183+
git_commit,
184+
git_add,
185+
git_get_repo_root,
186+
]

jupyter_ai_tools/toolkits/notebook.py

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from typing import Any, Dict, Literal, Optional, Tuple
77

88
import nbformat
9-
from jupyter_ai.tools.models import Tool, Toolkit
109
from jupyter_ydoc import YNotebook
1110
from pycrdt import Assoc, Text
1211

@@ -212,8 +211,8 @@ async def get_cell_id_from_index(file_path: str, cell_index: int) -> str:
212211

213212
async def add_cell(
214213
file_path: str,
215-
content: str | None = None,
216-
cell_id: str | None = None,
214+
content: Optional[str] = None,
215+
cell_id: Optional[str] = None,
217216
add_above: bool = False,
218217
cell_type: Literal["code", "markdown", "raw"] = "code",
219218
):
@@ -296,8 +295,8 @@ async def add_cell(
296295

297296
async def insert_cell(
298297
file_path: str,
299-
content: str | None = None,
300-
insert_index: int | None = None,
298+
content: Optional[str] = None,
299+
insert_index: Optional[int] = None,
301300
cell_type: Literal["code", "markdown", "raw"] = "code",
302301
):
303302
"""Inserts a new cell to the Jupyter notebook at the specified cell index.
@@ -888,7 +887,7 @@ def read_cell_nbformat(file_path: str, cell_id: str) -> Dict[str, Any]:
888887
raise ValueError(f"Cell with {cell_id=} not found in notebook at {file_path=}")
889888

890889

891-
def _get_cell_index_from_id_json(notebook_json, cell_id: str) -> int | None:
890+
def _get_cell_index_from_id_json(notebook_json, cell_id: str) -> Optional[int]:
892891
"""Get cell index from cell_id by notebook json dict.
893892
894893
Args:
@@ -906,7 +905,7 @@ def _get_cell_index_from_id_json(notebook_json, cell_id: str) -> int | None:
906905
return None
907906

908907

909-
def _get_cell_index_from_id_ydoc(ydoc, cell_id: str) -> int | None:
908+
def _get_cell_index_from_id_ydoc(ydoc, cell_id: str) -> Optional[int]:
910909
"""Get cell index from cell_id using YDoc interface.
911910
912911
Args:
@@ -925,7 +924,7 @@ def _get_cell_index_from_id_ydoc(ydoc, cell_id: str) -> int | None:
925924
return None
926925

927926

928-
def _get_cell_index_from_id_nbformat(notebook, cell_id: str) -> int | None:
927+
def _get_cell_index_from_id_nbformat(notebook, cell_id: str) -> Optional[int]:
929928
"""Get cell index from cell_id using nbformat interface.
930929
931930
Args:
@@ -1006,15 +1005,13 @@ async def create_notebook(file_path: str) -> str:
10061005
return f"Error: Failed to create notebook: {str(e)}"
10071006

10081007

1009-
toolkit = Toolkit(
1010-
name="notebook_toolkit",
1011-
description="Tools for reading and manipulating Jupyter notebooks.",
1012-
)
1013-
toolkit.add_tool(Tool(callable=read_notebook, read=True))
1014-
toolkit.add_tool(Tool(callable=read_cell, read=True))
1015-
toolkit.add_tool(Tool(callable=add_cell, read=True, write=True))
1016-
toolkit.add_tool(Tool(callable=insert_cell, read=True, write=True))
1017-
toolkit.add_tool(Tool(callable=delete_cell, delete=True))
1018-
toolkit.add_tool(Tool(callable=edit_cell, read=True, write=True))
1019-
toolkit.add_tool(Tool(callable=get_cell_id_from_index, read=True))
1020-
toolkit.add_tool(Tool(callable=create_notebook, write=True))
1008+
toolkit = [
1009+
read_notebook,
1010+
read_cell,
1011+
add_cell,
1012+
insert_cell,
1013+
delete_cell,
1014+
edit_cell,
1015+
get_cell_id_from_index,
1016+
create_notebook,
1017+
]

jupyter_ai_tools/utils.py

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22
import inspect
33
import os
44
from pathlib import Path
5-
from typing import Optional
5+
from typing import Dict, List, Optional
66
from urllib.parse import unquote
77

88
from jupyter_server.auth.identity import User
99
from jupyter_server.serverapp import ServerApp
1010
from pycrdt import Awareness
1111

12+
JSD_PRESENT = False
13+
try:
14+
import jupyter_server_documents # noqa: F401
15+
16+
JSD_PRESENT = True
17+
except ImportError:
18+
pass
19+
1220

1321
def get_serverapp():
1422
"""Returns the server app from the request context"""
@@ -66,6 +74,18 @@ def normalize_filepath(file_path: str) -> str:
6674
return str(resolved_path.resolve())
6775

6876

77+
async def get_room(room_id: str):
78+
"""Returns the yroom."""
79+
80+
serverapp = get_serverapp()
81+
if JSD_PRESENT:
82+
manager = serverapp.web_app.settings["yroom_manager"]
83+
return manager.get_room(room_id) if manager.has_room(room_id) else None
84+
else:
85+
manager = serverapp.web_app.settings["jupyter_server_ydoc"].ywebsocket_server
86+
return await manager.get_room(room_id) if manager.room_exists(room_id) else None
87+
88+
6989
async def get_jupyter_ydoc(file_id: str):
7090
"""Returns the notebook ydoc
7191
@@ -75,27 +95,31 @@ async def get_jupyter_ydoc(file_id: str):
7595
Returns:
7696
`YNotebook` ydoc for the notebook
7797
"""
78-
serverapp = get_serverapp()
79-
yroom_manager = serverapp.web_app.settings["yroom_manager"]
8098
room_id = f"json:notebook:{file_id}"
99+
yroom = await get_room(room_id)
100+
if not yroom:
101+
return
81102

82-
if yroom_manager.has_room(room_id):
83-
yroom = yroom_manager.get_room(room_id)
84-
notebook = await yroom.get_jupyter_ydoc()
85-
return notebook
103+
if JSD_PRESENT:
104+
return await yroom.get_jupyter_ydoc()
105+
else:
106+
return yroom._document
86107

87108

88109
async def get_global_awareness() -> Optional[Awareness]:
89-
serverapp = get_serverapp()
90-
yroom_manager = serverapp.web_app.settings["yroom_manager"]
110+
"""Returns the global awareness object for JupyterLab collaboration.
91111
92-
room_id = "JupyterLab:globalAwareness"
93-
if yroom_manager.has_room(room_id):
94-
yroom = yroom_manager.get_room(room_id)
95-
return yroom.get_awareness()
112+
Returns:
113+
Awareness instance for global collaboration, or None if not available
114+
"""
115+
yroom = await get_room("JupyterLab:globalAwareness")
116+
if not yroom:
117+
return None
96118

97-
# Return None if room doesn't exist
98-
return None
119+
if JSD_PRESENT:
120+
return yroom.get_awareness()
121+
else:
122+
return yroom.awareness
99123

100124

101125
async def get_file_id(file_path: str) -> str:
@@ -269,7 +293,7 @@ def notebook_json_to_md(notebook_json: dict, include_outputs: bool = True) -> st
269293
return "\n\n".join(md_parts)
270294

271295

272-
def metadata_to_md(metadata_json: dict) -> str:
296+
def metadata_to_md(metadata_json: Dict) -> str:
273297
"""Converts notebook or cell metadata to markdown string in YAML format.
274298
275299
Args:
@@ -339,7 +363,7 @@ def cell_to_md(cell_json: dict, index: int = 0, include_outputs: bool = True) ->
339363
return "\n\n".join(md_parts)
340364

341365

342-
def format_outputs(outputs: list) -> str:
366+
def format_outputs(outputs: List) -> str:
343367
"""Formats cell outputs into markdown.
344368
345369
Args:

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ classifiers = [
2323
dependencies = [
2424
"jupyter_server>=1.6,<3",
2525
"jupyterlab_git",
26-
"jupyter_ai>=3.0.0-beta.1"
26+
"pycrdt>=0.12.0,<0.13.0",
27+
"jupyter_ydoc>=3.0.0,<4.0.0",
2728
]
2829

2930

0 commit comments

Comments
 (0)