Skip to content

Commit 9d8f860

Browse files
committed
new: added filesystem_w namespace
1 parent 69e6520 commit 9d8f860

File tree

4 files changed

+125
-0
lines changed

4 files changed

+125
-0
lines changed

docs/namespaces.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,46 @@ jail:
139139

140140
</details>
141141

142+
## 📂 filesystem_w
143+
144+
Write primitives to the local filesystem.
145+
146+
### Jail
147+
148+
The tools in this namespace can be restricted to a specific set of paths by using the `jail` directive in the agent configuration:
149+
150+
```yaml
151+
using:
152+
- filesystem_w
153+
154+
jail:
155+
filesystem_w:
156+
- "/only/path/to/allow"
157+
- "{{ target_path }}" # variables can be used too
158+
```
159+
160+
<details>
161+
<summary><b>Show Tools</b></summary>
162+
163+
### `create_file`
164+
165+
<pre>Create a file on disk, if the file already exists, it will be overwritten.</pre>
166+
167+
**Parameters**
168+
169+
* `path` <i>(<class 'str'>)</i>: The path to the file to create
170+
* `content` <i>(str | None)</i>: The content to write to the file, if not provided, the file will be created empty
171+
172+
### `delete_file`
173+
174+
<pre>Delete a file from disk.</pre>
175+
176+
**Parameters**
177+
178+
* `path` <i>(<class 'str'>)</i>: The path to the file to delete
179+
180+
</details>
181+
142182
## 💬 inquire
143183

144184
Let the agent interactively ask questions to the user in a structured way.

nerve/generation/conversation.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ def __str__(self) -> str:
1313
return "<full history>"
1414

1515

16+
# TODO: add a Compression strategy that leaves the conversation structure but replaces
17+
# the tool responses bigger than a certain size with a <the content has been removed> placeholder.
18+
19+
1620
class SlidingWindowStrategy(WindowStrategy):
1721
def __init__(self, window_size: int = 10) -> None:
1822
self.window_size = window_size
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""
2+
Write primitives to the local filesystem.
3+
"""
4+
5+
import os
6+
from pathlib import Path
7+
from typing import Annotated
8+
9+
# for docs
10+
EMOJI = "📂"
11+
12+
# if set, the agent will only have access to these paths
13+
jail: list[str] = []
14+
15+
16+
# TODO: abstract and centralize jail system
17+
def _path_allowed(path_to_check: str) -> bool:
18+
if not jail:
19+
return True
20+
21+
# https://stackoverflow.com/questions/3812849/how-to-check-whether-a-directory-is-a-sub-directory-of-another-directory
22+
path = Path(path_to_check).resolve().absolute()
23+
for allowed_path in jail:
24+
allowed = Path(allowed_path).resolve().absolute()
25+
if path == allowed or allowed in path.parents:
26+
return True
27+
28+
return False
29+
30+
31+
def _path_acl(path_to_check: str) -> None:
32+
if not _path_allowed(path_to_check):
33+
raise ValueError(f"access to path {path_to_check} is not allowed, only allowed paths are: {jail}")
34+
35+
36+
def _maybe_text(output: bytes) -> str | bytes:
37+
try:
38+
return output.decode("utf-8").strip()
39+
except UnicodeDecodeError:
40+
return output
41+
42+
43+
def create_file(
44+
path: Annotated[str, "The path to the file to create"],
45+
content: Annotated[
46+
str | None, "The content to write to the file, if not provided, the file will be created empty"
47+
] = None,
48+
) -> str:
49+
"""Create a file on disk, if the file already exists, it will be overwritten."""
50+
51+
_path_acl(path)
52+
53+
response = ""
54+
55+
# ensure parent directory exists
56+
parent_dir = os.path.dirname(path)
57+
if parent_dir and not os.path.exists(parent_dir):
58+
os.makedirs(parent_dir)
59+
response += f"Created parent directory {parent_dir}.\n"
60+
61+
exists = os.path.exists(path)
62+
63+
with open(path, "w") as f:
64+
f.write(content or "")
65+
66+
if exists:
67+
response += f"File {path} updated.\n"
68+
else:
69+
response += f"File {path} created.\n"
70+
71+
return response
72+
73+
74+
def delete_file(path: Annotated[str, "The path to the file to delete"]) -> str:
75+
"""Delete a file from disk."""
76+
77+
_path_acl(path)
78+
79+
os.remove(path)
80+
return f"File {path} deleted."

nerve/tools/namespaces/shell.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def _maybe_text(output: bytes) -> str | bytes:
1616
return output
1717

1818

19+
# TODO: if both filesystem and shell are used, shell can be used to bypass the filesystem jailing system. find a way to either prevent it, or communicate it.
1920
def shell(
2021
command: Annotated[str, "The shell command to execute"],
2122
) -> str | bytes:

0 commit comments

Comments
 (0)