From 39a0880f644dcfc846b3d6d45605c6bba333f13a Mon Sep 17 00:00:00 2001 From: qin-ctx Date: Sat, 28 Feb 2026 16:37:23 +0800 Subject: [PATCH] =?UTF-8?q?fix(client):=20=E4=BF=AE=E5=A4=8D=E5=8D=95?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=9C=A8=E8=BF=9C=E7=A8=8Bserver=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E6=97=B6=E4=B8=8A=E4=BC=A0=E5=A4=B1=E8=B4=A5=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20(#331)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add-resource/add-skill 添加单个文件到远程 server 时,之前只处理了目录场景 (zip后上传),单文件直接传本地路径导致 server 端报 Path does not exist。 现在对 Python HTTP client 和 Rust CLI client 的 add_resource/add_skill 方法 均增加 is_file() 分支,单文件直接通过 _upload_temp_file 上传。 附带修复: - Rust CLI config 支持 OPENVIKING_CLI_CONFIG_FILE 环境变量(与 Python 侧一致) - Rust CLI add-resource --timeout 改为可选参数(仅配合 --wait 使用) Co-Authored-By: Claude Opus 4.6 --- crates/ov_cli/src/client.rs | 119 ++++++++++++++++++++++++---------- crates/ov_cli/src/config.rs | 10 +++ crates/ov_cli/src/main.rs | 8 +-- openviking_cli/client/http.py | 36 ++++++---- 4 files changed, 123 insertions(+), 50 deletions(-) diff --git a/crates/ov_cli/src/client.rs b/crates/ov_cli/src/client.rs index 8b7cf492..be9c6870 100644 --- a/crates/ov_cli/src/client.rs +++ b/crates/ov_cli/src/client.rs @@ -433,30 +433,63 @@ impl HttpClient { directly_upload_media: bool, ) -> Result { let path_obj = Path::new(path); - - // Check if it's a local directory and not a local server - if path_obj.exists() && path_obj.is_dir() && !self.is_local_server() { - // Zip the directory - let zip_file = self.zip_directory(path_obj)?; - let temp_path = self.upload_temp_file(zip_file.path()).await?; - - let body = serde_json::json!({ - "temp_path": temp_path, - "target": target, - "reason": reason, - "instruction": instruction, - "wait": wait, - "timeout": timeout, - "strict": strict, - "ignore_dirs": ignore_dirs, - "include": include, - "exclude": exclude, - "directly_upload_media": directly_upload_media, - }); - - self.post("/api/v1/resources", &body).await + + if path_obj.exists() && !self.is_local_server() { + if path_obj.is_dir() { + let zip_file = self.zip_directory(path_obj)?; + let temp_path = self.upload_temp_file(zip_file.path()).await?; + + let body = serde_json::json!({ + "temp_path": temp_path, + "target": target, + "reason": reason, + "instruction": instruction, + "wait": wait, + "timeout": timeout, + "strict": strict, + "ignore_dirs": ignore_dirs, + "include": include, + "exclude": exclude, + "directly_upload_media": directly_upload_media, + }); + + self.post("/api/v1/resources", &body).await + } else if path_obj.is_file() { + let temp_path = self.upload_temp_file(path_obj).await?; + + let body = serde_json::json!({ + "temp_path": temp_path, + "target": target, + "reason": reason, + "instruction": instruction, + "wait": wait, + "timeout": timeout, + "strict": strict, + "ignore_dirs": ignore_dirs, + "include": include, + "exclude": exclude, + "directly_upload_media": directly_upload_media, + }); + + self.post("/api/v1/resources", &body).await + } else { + let body = serde_json::json!({ + "path": path, + "target": target, + "reason": reason, + "instruction": instruction, + "wait": wait, + "timeout": timeout, + "strict": strict, + "ignore_dirs": ignore_dirs, + "include": include, + "exclude": exclude, + "directly_upload_media": directly_upload_media, + }); + + self.post("/api/v1/resources", &body).await + } } else { - // Regular case - use path directly let body = serde_json::json!({ "path": path, "target": target, @@ -470,7 +503,7 @@ impl HttpClient { "exclude": exclude, "directly_upload_media": directly_upload_media, }); - + self.post("/api/v1/resources", &body).await } } @@ -483,16 +516,34 @@ impl HttpClient { ) -> Result { let path_obj = Path::new(data); - if path_obj.exists() && path_obj.is_dir() && !self.is_local_server() { - let zip_file = self.zip_directory(path_obj)?; - let temp_path = self.upload_temp_file(zip_file.path()).await?; - - let body = serde_json::json!({ - "temp_path": temp_path, - "wait": wait, - "timeout": timeout, - }); - self.post("/api/v1/skills", &body).await + if path_obj.exists() && !self.is_local_server() { + if path_obj.is_dir() { + let zip_file = self.zip_directory(path_obj)?; + let temp_path = self.upload_temp_file(zip_file.path()).await?; + + let body = serde_json::json!({ + "temp_path": temp_path, + "wait": wait, + "timeout": timeout, + }); + self.post("/api/v1/skills", &body).await + } else if path_obj.is_file() { + let temp_path = self.upload_temp_file(path_obj).await?; + + let body = serde_json::json!({ + "temp_path": temp_path, + "wait": wait, + "timeout": timeout, + }); + self.post("/api/v1/skills", &body).await + } else { + let body = serde_json::json!({ + "data": data, + "wait": wait, + "timeout": timeout, + }); + self.post("/api/v1/skills", &body).await + } } else { let body = serde_json::json!({ "data": data, diff --git a/crates/ov_cli/src/config.rs b/crates/ov_cli/src/config.rs index 299b31eb..57a81789 100644 --- a/crates/ov_cli/src/config.rs +++ b/crates/ov_cli/src/config.rs @@ -3,6 +3,8 @@ use std::path::PathBuf; use crate::error::{Error, Result}; +const OPENVIKING_CLI_CONFIG_ENV: &str = "OPENVIKING_CLI_CONFIG_FILE"; + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { #[serde(default = "default_url")] @@ -53,6 +55,14 @@ impl Config { } pub fn load_default() -> Result { + // Resolution order: env var > default path + if let Ok(env_path) = std::env::var(OPENVIKING_CLI_CONFIG_ENV) { + let p = PathBuf::from(env_path); + if p.exists() { + return Self::from_file(&p.to_string_lossy()); + } + } + let config_path = default_config_path()?; if config_path.exists() { Self::from_file(&config_path.to_string_lossy()) diff --git a/crates/ov_cli/src/main.rs b/crates/ov_cli/src/main.rs index 1f6b59a4..affb78bf 100644 --- a/crates/ov_cli/src/main.rs +++ b/crates/ov_cli/src/main.rs @@ -74,9 +74,9 @@ enum Commands { /// Wait until processing is complete #[arg(long)] wait: bool, - /// Wait timeout in seconds + /// Wait timeout in seconds (only used with --wait) #[arg(long)] - timeout: f64, + timeout: Option, /// No strict mode for directory scanning #[arg(long = "no-strict", default_value_t = false)] no_strict: bool, @@ -588,7 +588,7 @@ async fn handle_add_resource( reason: String, instruction: String, wait: bool, - timeout: f64, + timeout: Option, no_strict: bool, ignore_dirs: Option, include: Option, @@ -635,7 +635,7 @@ async fn handle_add_resource( reason, instruction, wait, - Some(timeout), + timeout, strict, ignore_dirs, include, diff --git a/openviking_cli/client/http.py b/openviking_cli/client/http.py index 20c7c2e9..e63a3a40 100644 --- a/openviking_cli/client/http.py +++ b/openviking_cli/client/http.py @@ -304,13 +304,19 @@ async def add_resource( } path_obj = Path(path) - if path_obj.exists() and path_obj.is_dir() and not self._is_local_server(): - zip_path = self._zip_directory(path) - try: - temp_path = await self._upload_temp_file(zip_path) + if path_obj.exists() and not self._is_local_server(): + if path_obj.is_dir(): + zip_path = self._zip_directory(path) + try: + temp_path = await self._upload_temp_file(zip_path) + request_data["temp_path"] = temp_path + finally: + Path(zip_path).unlink(missing_ok=True) + elif path_obj.is_file(): + temp_path = await self._upload_temp_file(path) request_data["temp_path"] = temp_path - finally: - Path(zip_path).unlink(missing_ok=True) + else: + request_data["path"] = path else: request_data["path"] = path @@ -334,13 +340,19 @@ async def add_skill( if isinstance(data, str): path_obj = Path(data) - if path_obj.exists() and path_obj.is_dir() and not self._is_local_server(): - zip_path = self._zip_directory(data) - try: - temp_path = await self._upload_temp_file(zip_path) + if path_obj.exists() and not self._is_local_server(): + if path_obj.is_dir(): + zip_path = self._zip_directory(data) + try: + temp_path = await self._upload_temp_file(zip_path) + request_data["temp_path"] = temp_path + finally: + Path(zip_path).unlink(missing_ok=True) + elif path_obj.is_file(): + temp_path = await self._upload_temp_file(data) request_data["temp_path"] = temp_path - finally: - Path(zip_path).unlink(missing_ok=True) + else: + request_data["data"] = data else: request_data["data"] = data else: