Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions crates/ov_cli/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,8 @@ impl HttpClient {
pub async fn add_resource(
&self,
path: &str,
target: Option<String>,
to: Option<String>,
parent: Option<String>,
reason: &str,
instruction: &str,
wait: bool,
Expand All @@ -444,7 +445,8 @@ impl HttpClient {

let body = serde_json::json!({
"temp_path": temp_path,
"target": target,
"to": to,
"parent": parent,
"reason": reason,
"instruction": instruction,
"wait": wait,
Expand All @@ -462,7 +464,8 @@ impl HttpClient {

let body = serde_json::json!({
"temp_path": temp_path,
"target": target,
"to": to,
"parent": parent,
"reason": reason,
"instruction": instruction,
"wait": wait,
Expand All @@ -478,7 +481,8 @@ impl HttpClient {
} else {
let body = serde_json::json!({
"path": path,
"target": target,
"to": to,
"parent": parent,
"reason": reason,
"instruction": instruction,
"wait": wait,
Expand All @@ -495,7 +499,8 @@ impl HttpClient {
} else {
let body = serde_json::json!({
"path": path,
"target": target,
"to": to,
"parent": parent,
"reason": reason,
"instruction": instruction,
"wait": wait,
Expand Down
2 changes: 2 additions & 0 deletions crates/ov_cli/src/commands/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub async fn add_resource(
client: &HttpClient,
path: &str,
to: Option<String>,
parent: Option<String>,
reason: String,
instruction: String,
wait: bool,
Expand All @@ -22,6 +23,7 @@ pub async fn add_resource(
.add_resource(
path,
to,
parent,
&reason,
&instruction,
wait,
Expand Down
17 changes: 15 additions & 2 deletions crates/ov_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,12 @@ enum Commands {
AddResource {
/// Local path or URL to import
path: String,
/// Target URI
/// Exact target URI (must not exist yet) (cannot be used with --parent)
#[arg(long)]
to: Option<String>,
/// Target parent URI (must already exist and be a directory) (cannot be used with --to)
#[arg(long)]
parent: Option<String>,
/// Reason for import
#[arg(long, default_value = "")]
reason: String,
Expand Down Expand Up @@ -495,6 +498,7 @@ async fn main() {
Commands::AddResource {
path,
to,
parent,
reason,
instruction,
wait,
Expand All @@ -508,6 +512,7 @@ async fn main() {
handle_add_resource(
path,
to,
parent,
reason,
instruction,
wait,
Expand Down Expand Up @@ -622,6 +627,7 @@ async fn main() {
async fn handle_add_resource(
mut path: String,
to: Option<String>,
parent: Option<String>,
reason: String,
instruction: String,
wait: bool,
Expand Down Expand Up @@ -663,7 +669,13 @@ async fn handle_add_resource(
}
path = unescaped_path;
}


// Check that only one of --to or --parent is set
if to.is_some() && parent.is_some() {
eprintln!("Error: Cannot specify both --to and --parent at the same time.");
std::process::exit(1);
}

let strict = !no_strict;
let directly_upload_media = !no_directly_upload_media;

Expand All @@ -672,6 +684,7 @@ async fn handle_add_resource(
&client,
&path,
to,
parent,
reason,
instruction,
wait,
Expand Down
14 changes: 11 additions & 3 deletions openviking/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""

from __future__ import annotations

import threading
from typing import Any, Dict, List, Optional, Union

Expand Down Expand Up @@ -173,7 +174,8 @@ async def commit_session(self, session_id: str) -> Dict[str, Any]:
async def add_resource(
self,
path: str,
target: Optional[str] = None,
to: Optional[str] = None,
parent: Optional[str] = None,
reason: str = "",
instruction: str = "",
wait: bool = False,
Expand All @@ -190,15 +192,21 @@ async def add_resource(
reason: Context/reason for adding this resource.
instruction: Specific instruction for processing.
wait: If True, wait for processing to complete.
target: Target path in VikingFS (e.g., "kb/docs").
to: Exact target URI (must not exist yet).
parent: Target parent URI (must already exist).
build_index: Whether to build vector index immediately (default: True).
summarize: Whether to generate summary (default: False).
"""
await self._ensure_initialized()

# Validate that only one of 'to' or 'parent' is set
if to and parent:
raise ValueError("Cannot specify both 'to' and 'parent' at the same time.")

return await self._client.add_resource(
path=path,
target=target,
to=to,
parent=parent,
reason=reason,
instruction=instruction,
wait=wait,
Expand Down
10 changes: 8 additions & 2 deletions openviking/client/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,24 @@ async def close(self) -> None:
async def add_resource(
self,
path: str,
target: Optional[str] = None,
to: Optional[str] = None,
parent: Optional[str] = None,
reason: str = "",
instruction: str = "",
wait: bool = False,
timeout: Optional[float] = None,
**kwargs,
) -> Dict[str, Any]:
"""Add resource to OpenViking."""
# Validate that only one of 'to' or 'parent' is set
if to and parent:
raise ValueError("Cannot specify both 'to' and 'parent' at the same time.")

return await self._service.resources.add_resource(
path=path,
ctx=self._ctx,
target=target,
to=to,
parent=parent,
reason=reason,
instruction=instruction,
wait=wait,
Expand Down
48 changes: 26 additions & 22 deletions openviking/parse/tree_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ async def finalize_from_temp(
temp_dir_path: str,
ctx: RequestContext,
scope: str = "resources",
base_uri: Optional[str] = None,
to_uri: Optional[str] = None,
parent_uri: Optional[str] = None,
source_path: Optional[str] = None,
source_format: Optional[str] = None,
trigger_semantic: bool = False,
Expand All @@ -98,6 +99,8 @@ async def finalize_from_temp(
Finalize processing by moving from temp to AGFS.

Args:
to_uri: Exact target URI (must not exist)
parent_uri: Target parent URI (must exist)
trigger_semantic: Whether to automatically trigger semantic generation.
Default is False (handled by ResourceProcessor/Summarizer).
"""
Expand Down Expand Up @@ -132,30 +135,31 @@ async def finalize_from_temp(

# 2. Determine base_uri and final document name with org/repo for GitHub/GitLab
auto_base_uri = self._get_base_uri(scope, source_path, source_format)

# 3. Check if base_uri exists - if it does, use it as parent directory
base_exists = False
if base_uri:
base_uri = parent_uri or auto_base_uri
# 3. Determine candidate_uri
if to_uri:
# Exact target URI: must not exist yet
try:
await viking_fs.stat(base_uri)
base_exists = True
await viking_fs.stat(to_uri, ctx=ctx)
# If we get here, it already exists
raise FileExistsError(f"Target URI already exists: {to_uri}")
except FileExistsError:
raise
except Exception:
base_exists = False

if base_exists:
if "/" in final_doc_name:
repo_name_only = final_doc_name.split("/")[-1]
else:
repo_name_only = final_doc_name
candidate_uri = VikingURI(base_uri or auto_base_uri).join(repo_name_only).uri
# It doesn't exist, good to use
pass
candidate_uri = to_uri
else:
if "/" in final_doc_name:
parts = final_doc_name.split("/")
sanitized_parts = [VikingURI.sanitize_segment(p) for p in parts if p]
base_viking_uri = VikingURI(base_uri or auto_base_uri)
candidate_uri = VikingURI.build(base_viking_uri.scope, *sanitized_parts)
else:
candidate_uri = VikingURI(base_uri or auto_base_uri).join(doc_name).uri
if parent_uri:
# Parent URI must exist and be a directory
try:
stat_result = await viking_fs.stat(parent_uri, ctx=ctx)
if not stat_result.get("isDir"):
raise ValueError(f"Parent URI is not a directory: {parent_uri}")
except Exception as e:
raise FileNotFoundError(f"Parent URI does not exist: {parent_uri}") from e
candidate_uri = VikingURI(base_uri).join(final_doc_name).uri

final_uri = await self._resolve_unique_uri(candidate_uri, ctx=ctx)

if final_uri != candidate_uri:
Expand Down
13 changes: 11 additions & 2 deletions openviking/server/routers/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from openviking.server.dependencies import get_service
from openviking.server.identity import RequestContext
from openviking.server.models import Response
from openviking_cli.exceptions import InvalidArgumentError
from openviking_cli.utils.config.open_viking_config import get_openviking_config

router = APIRouter(prefix="/api/v1", tags=["resources"])
Expand All @@ -24,7 +25,8 @@ class AddResourceRequest(BaseModel):

path: Optional[str] = None
temp_path: Optional[str] = None
target: Optional[str] = None
to: Optional[str] = None
parent: Optional[str] = None
reason: str = ""
instruction: str = ""
wait: bool = False
Expand Down Expand Up @@ -95,16 +97,23 @@ async def add_resource(
_ctx: RequestContext = Depends(get_request_context),
):
"""Add resource to OpenViking."""
# Validate request: only one of 'to' or 'parent' can be set
if request.to and request.parent:
raise InvalidArgumentError("Cannot specify both 'to' and 'parent' at the same time.")

service = get_service()

path = request.path
if request.temp_path:
path = request.temp_path
if path is None:
raise InvalidArgumentError("Either 'path' or 'temp_path' must be provided.")

result = await service.resources.add_resource(
path=path,
ctx=_ctx,
target=request.target,
to=request.to,
parent=request.parent,
reason=request.reason,
instruction=request.instruction,
wait=request.wait,
Expand Down
16 changes: 12 additions & 4 deletions openviking/service/resource_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ async def add_resource(
self,
path: str,
ctx: RequestContext,
target: Optional[str] = None,
to: Optional[str] = None,
parent: Optional[str] = None,
reason: str = "",
instruction: str = "",
wait: bool = False,
Expand Down Expand Up @@ -94,8 +95,14 @@ async def add_resource(
self._ensure_initialized()

# add_resource only supports resources scope
if target and target.startswith("viking://"):
parsed = VikingURI(target)
if to and to.startswith("viking://"):
parsed = VikingURI(to)
if parsed.scope != "resources":
raise InvalidArgumentError(
f"add_resource only supports resources scope, use dedicated interface to add {parsed.scope} content"
)
if parent and parent.startswith("viking://"):
parsed = VikingURI(parent)
if parsed.scope != "resources":
raise InvalidArgumentError(
f"add_resource only supports resources scope, use dedicated interface to add {parsed.scope} content"
Expand All @@ -107,7 +114,8 @@ async def add_resource(
reason=reason,
instruction=instruction,
scope="resources",
target=target,
to=to,
parent=parent,
build_index=build_index,
summarize=summarize,
**kwargs,
Expand Down
7 changes: 5 additions & 2 deletions openviking/sync_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Dict, List, Optional

if TYPE_CHECKING:
Expand Down Expand Up @@ -79,7 +80,8 @@ def commit_session(self, session_id: str) -> Dict[str, Any]:
def add_resource(
self,
path: str,
target: Optional[str] = None,
to: Optional[str] = None,
parent: Optional[str] = None,
reason: str = "",
instruction: str = "",
wait: bool = False,
Expand All @@ -99,7 +101,8 @@ def add_resource(
return run_async(
self._async_client.add_resource(
path=path,
target=target,
to=to,
parent=parent,
reason=reason,
instruction=instruction,
wait=wait,
Expand Down
Loading
Loading