@@ -70,12 +70,12 @@ def parse_sources(cls, v: list) -> list:
7070 return result
7171
7272
73- async def __is_context_chat_available (nc : AsyncNextcloudApp , memories_folder_path : str ):
73+ async def __is_context_chat_available (nc : AsyncNextcloudApp ):
7474 tasktypes = (await nc .ocs ('GET' , '/ocs/v2.php/taskprocessing/tasktypes' ))['types' ].keys ()
7575 return CONTEXT_CHAT_SEARCH_TASK_TYPE in tasktypes
7676
7777
78- def __validate_memory_path (path : str , memories_folder_path : str , * , allow_folder_path : bool = False ) -> tuple [str , str ]:
78+ def __validate_memory_path (path : str , memories_folder_path : str , * , only_allow_folder_path : bool = False ) -> tuple [str , str ]:
7979 """returns tuple[full_path, memories_scoped_path]"""
8080 if not path :
8181 raise AgentFacingError ('Memory path cannot be empty' )
@@ -90,11 +90,15 @@ def __validate_memory_path(path: str, memories_folder_path: str, *, allow_folder
9090 if '/..' in candidate or '../' in candidate or '..\\ ' in candidate :
9191 raise RuntimeError ('Agent tried to access directories beyond the memories folder' )
9292
93- if not allow_folder_path and (not decoded .endswith ('.md' ) and not decoded .endswith ('.markdown' )):
93+ if not only_allow_folder_path and (not decoded .endswith ('.md' ) and not decoded .endswith ('.markdown' )):
9494 raise AgentFacingError ('Memory file should be a markdown file' )
9595
96+ if only_allow_folder_path and (decoded .endswith ('.md' ) or decoded .endswith ('.markdown' )):
97+ raise AgentFacingError ('Memory path should point to a folder, not a memory file.' )
98+
9699 path_parts = [p for p in decoded .strip ('/' ).split ('/' ) if p ]
97- if len (path_parts ) > MAX_MEMORY_FOLDER_DEPTH + 1 : # +1 for the filename itself
100+ max_parts = MAX_MEMORY_FOLDER_DEPTH if only_allow_folder_path else MAX_MEMORY_FOLDER_DEPTH + 1 # +1 for the filename itself
101+ if len (path_parts ) > max_parts :
98102 raise AgentFacingError (f'Memory path exceeds maximum depth of { MAX_MEMORY_FOLDER_DEPTH } ' )
99103
100104 # resolve the full absolute path and verify it stays within the memory folder
@@ -143,7 +147,7 @@ async def __create_folders_if_not_exists(nc: AsyncNextcloudApp, adapter: niquest
143147 )
144148 if propfind .status_code == 404 :
145149 base_parts = [p for p in base_memories_path .split ('/' ) if p ]
146- all_parts = base_parts + scoped_folder_parts
150+ all_parts = base_parts + [ quote ( p , safe = '' ) for p in scoped_folder_parts ]
147151 for i in range (len (base_parts ) + 1 , len (all_parts ) + 1 ):
148152 folder_path = '/' .join (all_parts [:i ])
149153 r = await adapter .request ('MKCOL' , f"{ nc .app_cfg .endpoint } /remote.php/dav/files/{ user_id } /{ folder_path } " )
@@ -314,7 +318,7 @@ async def delete_memory_folder(path: str):
314318 :return: Status of the deletion operation.
315319 """
316320 try :
317- full_path , _ = __validate_memory_path (path , memories_folder_path , allow_folder_path = True )
321+ full_path , _ = __validate_memory_path (path , memories_folder_path , only_allow_folder_path = True )
318322 except AgentFacingError as e :
319323 return {'error' : str (e )}
320324 except Exception as e :
@@ -344,6 +348,10 @@ async def search_memories(query: str, k: int = 5) -> list[dict[str, str]]:
344348 files_handle = AsyncFilesAPI (nc ._session )
345349 memories_folder_fsnode = await files_handle .by_path (memories_folder_path )
346350
351+ if memories_folder_fsnode is None :
352+ await log (nc , logging .WARNING , f'Could not find the memories folder: { memories_folder_path } ' )
353+ raise RuntimeError ('Could not find the memories folder, try creating a memory first.' )
354+
347355 task_input = {
348356 'prompt' : query ,
349357 'scopeType' : 'source' ,
@@ -367,14 +375,12 @@ async def fetch(file_id: int) -> dict[str, str]:
367375 :return: {'path': string, 'content: string}
368376 """
369377 fsnode = await files_handle .by_id (file_id )
370- filepath = '/' + fsnode .user_path .removeprefix (prefix ).rstrip ('/' )
371-
372378 if fsnode is None :
373379 await log (nc , logging .WARNING , f'Could not fetch file by id: { file_id } ' )
374- return {'path' : filepath , 'content' : '' }
380+ return {'path' : '' , 'content' : '' }
375381
376382 return {
377- 'path' : filepath ,
383+ 'path' : '/' + fsnode . user_path . removeprefix ( prefix ). rstrip ( '/' ) ,
378384 'content' : (await files_handle .download (fsnode )).decode (errors = 'replace' ),
379385 }
380386
@@ -387,7 +393,7 @@ async def fetch(file_id: int) -> dict[str, str]:
387393 store_memory ,
388394 delete_memory ,
389395 delete_memory_folder ,
390- * ([search_memories ] if await __is_context_chat_available (nc , memories_folder_path ) else []),
396+ * ([search_memories ] if await __is_context_chat_available (nc ) else []),
391397 ] if assistant_folder_path else []),
392398 ]
393399
0 commit comments