@@ -276,3 +276,80 @@ def vector_store_create_file_from_path(
276276 )
277277 LOGGER .info (f"Addded uploaded file (filename{ uploaded_file .filename } to vector store { vector_store .id } " )
278278 return vs_file
279+
280+
281+ def vector_store_upload_doc_sources (
282+ doc_sources : Any ,
283+ repo_root : Path ,
284+ llama_stack_client : LlamaStackClient ,
285+ vector_store : Any ,
286+ vector_io_provider : str ,
287+ ) -> None :
288+ """Upload parametrized document sources (URLs and repo-local paths) to a vector store.
289+
290+ Resolves each local path under ``repo_root`` and re-resolves directory entries to avoid
291+ symlink escape outside the repository.
292+
293+ Args:
294+ doc_sources: List of URL or path strings (repo-relative or absolute under repo root).
295+ repo_root: Resolved repository root; local paths must resolve under this directory.
296+ llama_stack_client: Client used for file and vector store APIs.
297+ vector_store: Target vector store (must expose ``id``).
298+ vector_io_provider: Provider id for log context only.
299+
300+ Raises:
301+ TypeError: If ``doc_sources`` is not a list.
302+ ValueError: If a local path resolves outside ``repo_root``.
303+ FileNotFoundError: If a file or non-empty directory source is missing.
304+ """
305+ if not isinstance (doc_sources , list ):
306+ raise TypeError (f"doc_sources must be a list[str], got { type (doc_sources ).__name__ } " )
307+ LOGGER .info (
308+ "Uploading doc_sources to vector_store (provider_id=%s, id=%s): %s" ,
309+ vector_io_provider ,
310+ vector_store .id ,
311+ doc_sources ,
312+ )
313+ repo_root_resolved = repo_root .resolve ()
314+ for source in doc_sources :
315+ if source .startswith (("http://" , "https://" )):
316+ vector_store_create_file_from_url (
317+ url = source ,
318+ llama_stack_client = llama_stack_client ,
319+ vector_store = vector_store ,
320+ )
321+ continue
322+ raw_path = Path (source ) # noqa: FCN001
323+ resolved_source = raw_path .resolve () if raw_path .is_absolute () else (repo_root_resolved / raw_path ).resolve ()
324+ if not resolved_source .is_relative_to (repo_root_resolved ):
325+ raise ValueError (
326+ f"doc_sources path must be under repo root ({ repo_root_resolved } ): { source !r} " ,
327+ )
328+ source_path = resolved_source
329+
330+ if source_path .is_dir ():
331+ files = sorted (source_path .iterdir ())
332+ if not files :
333+ raise FileNotFoundError (f"No files found in directory: { source_path } " )
334+ for file_path in files :
335+ file_path_resolved = file_path .resolve (strict = True )
336+ if not file_path_resolved .is_relative_to (repo_root_resolved ):
337+ raise ValueError (
338+ f"doc_sources directory entry must resolve under repo root "
339+ f"({ repo_root_resolved } ): { file_path !r} -> { file_path_resolved !r} " ,
340+ )
341+ if not file_path_resolved .is_file ():
342+ continue
343+ vector_store_create_file_from_path (
344+ file_path = file_path_resolved ,
345+ llama_stack_client = llama_stack_client ,
346+ vector_store = vector_store ,
347+ )
348+ elif source_path .is_file ():
349+ vector_store_create_file_from_path (
350+ file_path = source_path ,
351+ llama_stack_client = llama_stack_client ,
352+ vector_store = vector_store ,
353+ )
354+ else :
355+ raise FileNotFoundError (f"Document source not found: { source_path } " )
0 commit comments