Skip to content

Commit 8101881

Browse files
kyteinskyjulien-nc
andauthored
feat: add Nextcloud file tree tool (#196)
* fix: use nc_py_api to get the Nextcloud file tree Signed-off-by: kyteinsky <kyteinsky@gmail.com> * add get_folder_tree back Signed-off-by: kyteinsky <kyteinsky@gmail.com> * Update ex_app/lib/all_tools/files.py Co-authored-by: Julien Veyssier <julien-nc@posteo.net> Signed-off-by: Anupam Kumar <kyteinsky@gmail.com> --------- Signed-off-by: kyteinsky <kyteinsky@gmail.com> Signed-off-by: Anupam Kumar <kyteinsky@gmail.com> Co-authored-by: Julien Veyssier <julien-nc@posteo.net>
1 parent 9dd1af3 commit 8101881

1 file changed

Lines changed: 58 additions & 4 deletions

File tree

ex_app/lib/all_tools/files.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
22
# SPDX-License-Identifier: AGPL-3.0-or-later
3+
import niquests
34
from langchain_core.tools import tool
45
from nc_py_api import AsyncNextcloudApp
5-
import niquests
6+
from nc_py_api.files.files_async import AsyncFilesAPI, FsNode
67

8+
from ex_app.lib.all_tools.lib.decorator import dangerous_tool, safe_tool
79
from ex_app.lib.all_tools.lib.files import get_file_id_from_file_url
810

9-
from ex_app.lib.all_tools.lib.decorator import safe_tool, dangerous_tool
10-
1111

1212
def _validate_path(path: str) -> str:
1313
"""Reject path traversal attempts in a user-supplied file path."""
@@ -61,11 +61,64 @@ async def get_file_content_by_file_link(file_url: str):
6161

6262
return response.text
6363

64+
def __format_fs_node(fsnode: FsNode) -> dict:
65+
# todo: permissions info
66+
return {
67+
'path': fsnode.user_path,
68+
'file_id': fsnode.info.fileid,
69+
'etag': fsnode.etag.replace('"', '').replace("'", ''),
70+
'bytes': fsnode.info.size,
71+
'creation_date': fsnode.info.creation_date.isoformat(),
72+
'last_modified': fsnode.info.last_modified.isoformat(),
73+
'mimetype': fsnode.info.mimetype,
74+
'is_shared': fsnode.is_shared,
75+
'is_favourite': fsnode.info.favorite,
76+
'is_version': fsnode.info.is_version,
77+
'trash_info': {
78+
'in_trash': fsnode.info.in_trash,
79+
**({
80+
'trashbin_filename': fsnode.info.trashbin_filename,
81+
'original_location': fsnode.info.trashbin_original_location,
82+
'deletion_time': fsnode.info.trashbin_deletion_time,
83+
} if fsnode.info.in_trash else {}),
84+
},
85+
'lock_info': {
86+
'is_locked': fsnode.lock_info.is_locked,
87+
**({
88+
'owner': fsnode.lock_info.owner,
89+
'owner_display_name': fsnode.lock_info.owner_display_name,
90+
'type': fsnode.lock_info.type.name,
91+
'creation_time': fsnode.lock_info.lock_creation_time,
92+
'ttl': fsnode.lock_info.lock_ttl,
93+
'locked_by_app': fsnode.lock_info.owner_editor,
94+
} if fsnode.lock_info.is_locked else {}),
95+
},
96+
}
97+
98+
99+
@tool
100+
@safe_tool
101+
async def get_file_tree(path: str = '/', include_metadata = False, depth: int = 1):
102+
"""
103+
Get the file tree of the user (lists the folders and files the user has in Nextcloud Files)
104+
:param path: the path to enumerate. It should be relative to the root directory like /Media and NOT /userid/files/Media
105+
:param include_metadata: include the etag, file/folder id, last modified times, etc. with the file/folder paths
106+
:param depth: how many directory levels should be included in output. Default = 1 (only specified directory). Max depth = 5.
107+
:return:
108+
"""
109+
110+
files_handle = AsyncFilesAPI(nc._session)
111+
fsnode_list = await files_handle.listdir(path, min(5, depth))
112+
if include_metadata:
113+
return [__format_fs_node(fsnode) for fsnode in fsnode_list]
114+
115+
return [fsnode.user_path for fsnode in fsnode_list]
116+
64117
@tool
65118
@safe_tool
66119
async def get_folder_tree(depth: int):
67120
"""
68-
Get the folder tree of the user (lists the files the user has in Nextcloud Files)
121+
Get the folder tree of the user (lists only the folders the user has in Nextcloud Files)
69122
:param depth: the depth of the returned folder tree
70123
:return:
71124
"""
@@ -182,6 +235,7 @@ async def delete_file(path: str):
182235
return [
183236
get_file_content,
184237
get_file_content_by_file_link,
238+
get_file_tree,
185239
get_folder_tree,
186240
create_public_sharing_link,
187241
upload_file,

0 commit comments

Comments
 (0)