Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
105 changes: 68 additions & 37 deletions crates/ov_cli/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use std::fs::File;
use std::path::Path;
use tempfile::NamedTempFile;
use url::Url;
use zip::write::FileOptions;
use zip::CompressionMethod;
use zip::write::FileOptions;

use crate::error::{Error, Result};

Expand Down Expand Up @@ -62,7 +62,8 @@ impl HttpClient {
let temp_file = NamedTempFile::new()?;
let file = File::create(temp_file.path())?;
let mut zip = zip::ZipWriter::new(file);
let options: FileOptions<'_, ()> = FileOptions::default().compression_method(CompressionMethod::Deflated);
let options: FileOptions<'_, ()> =
FileOptions::default().compression_method(CompressionMethod::Deflated);

let walkdir = walkdir::WalkDir::new(dir_path);
for entry in walkdir.into_iter().filter_map(|e| e.ok()) {
Expand All @@ -89,14 +90,13 @@ impl HttpClient {

// Read file content
let file_content = tokio::fs::read(file_path).await?;

// Create multipart form
let part = reqwest::multipart::Part::bytes(file_content)
.file_name(file_name.to_string());

let part = part.mime_str("application/octet-stream").map_err(|e| {
Error::Network(format!("Failed to set mime type: {}", e))
})?;
let part = reqwest::multipart::Part::bytes(file_content).file_name(file_name.to_string());

let part = part
.mime_str("application/octet-stream")
.map_err(|e| Error::Network(format!("Failed to set mime type: {}", e)))?;

let form = reqwest::multipart::Form::new().part("file", part);

Expand Down Expand Up @@ -235,10 +235,7 @@ impl HttpClient {
self.handle_response(response).await
}

async fn handle_response<T: DeserializeOwned>(
&self,
response: reqwest::Response,
) -> Result<T> {
async fn handle_response<T: DeserializeOwned>(&self, response: reqwest::Response) -> Result<T> {
let status = response.status();

// Handle empty response (204 No Content, etc.)
Expand All @@ -259,7 +256,11 @@ impl HttpClient {
.and_then(|e| e.get("message"))
.and_then(|m| m.as_str())
.map(|s| s.to_string())
.or_else(|| json.get("detail").and_then(|d| d.as_str()).map(|s| s.to_string()))
.or_else(|| {
json.get("detail")
.and_then(|d| d.as_str())
.map(|s| s.to_string())
})
.unwrap_or_else(|| format!("HTTP error {}", status));
return Err(Error::Api(error_msg));
}
Expand Down Expand Up @@ -307,7 +308,12 @@ impl HttpClient {
self.get("/api/v1/content/overview", &params).await
}

pub async fn reindex(&self, uri: &str, regenerate: bool, wait: bool) -> Result<serde_json::Value> {
pub async fn reindex(
&self,
uri: &str,
regenerate: bool,
wait: bool,
) -> Result<serde_json::Value> {
let body = serde_json::json!({
"uri": uri,
"regenerate": regenerate,
Expand All @@ -320,7 +326,7 @@ impl HttpClient {
pub async fn get_bytes(&self, uri: &str) -> Result<Vec<u8>> {
let url = format!("{}/api/v1/content/download", self.base_url);
let params = vec![("uri".to_string(), uri.to_string())];

let response = self
.http
.get(&url)
Expand All @@ -337,20 +343,22 @@ impl HttpClient {
.json()
.await
.map_err(|e| Error::Network(format!("Failed to parse error response: {}", e)));

let error_msg = match json_result {
Ok(json) => {
json
.get("error")
.and_then(|e| e.get("message"))
.and_then(|m| m.as_str())
.map(|s| s.to_string())
.or_else(|| json.get("detail").and_then(|d| d.as_str()).map(|s| s.to_string()))
.unwrap_or_else(|| format!("HTTP error {}", status))
}
Ok(json) => json
.get("error")
.and_then(|e| e.get("message"))
.and_then(|m| m.as_str())
.map(|s| s.to_string())
.or_else(|| {
json.get("detail")
.and_then(|d| d.as_str())
.map(|s| s.to_string())
})
.unwrap_or_else(|| format!("HTTP error {}", status)),
Err(_) => format!("HTTP error {}", status),
};

return Err(Error::Api(error_msg));
}

Expand All @@ -363,7 +371,16 @@ impl HttpClient {

// ============ Filesystem Methods ============

pub async fn ls(&self, uri: &str, simple: bool, recursive: bool, output: &str, abs_limit: i32, show_all_hidden: bool, node_limit: i32) -> Result<serde_json::Value> {
pub async fn ls(
&self,
uri: &str,
simple: bool,
recursive: bool,
output: &str,
abs_limit: i32,
show_all_hidden: bool,
node_limit: i32,
) -> Result<serde_json::Value> {
let params = vec![
("uri".to_string(), uri.to_string()),
("simple".to_string(), simple.to_string()),
Expand All @@ -376,7 +393,15 @@ impl HttpClient {
self.get("/api/v1/fs/ls", &params).await
}

pub async fn tree(&self, uri: &str, output: &str, abs_limit: i32, show_all_hidden: bool, node_limit: i32, level_limit: i32) -> Result<serde_json::Value> {
pub async fn tree(
&self,
uri: &str,
output: &str,
abs_limit: i32,
show_all_hidden: bool,
node_limit: i32,
level_limit: i32,
) -> Result<serde_json::Value> {
let params = vec![
("uri".to_string(), uri.to_string()),
("output".to_string(), output.to_string()),
Expand Down Expand Up @@ -453,7 +478,13 @@ impl HttpClient {
self.post("/api/v1/search/search", &body).await
}

pub async fn grep(&self, uri: &str, pattern: &str, ignore_case: bool, node_limit: i32) -> Result<serde_json::Value> {
pub async fn grep(
&self,
uri: &str,
pattern: &str,
ignore_case: bool,
node_limit: i32,
) -> Result<serde_json::Value> {
let body = serde_json::json!({
"uri": uri,
"pattern": pattern,
Expand All @@ -463,8 +494,12 @@ impl HttpClient {
self.post("/api/v1/search/grep", &body).await
}


pub async fn glob(&self, pattern: &str, uri: &str, node_limit: i32) -> Result<serde_json::Value> {
pub async fn glob(
&self,
pattern: &str,
uri: &str,
node_limit: i32,
) -> Result<serde_json::Value> {
let body = serde_json::json!({
"pattern": pattern,
"uri": uri,
Expand Down Expand Up @@ -737,11 +772,7 @@ impl HttpClient {
self.put(&path, &body).await
}

pub async fn admin_regenerate_key(
&self,
account_id: &str,
user_id: &str,
) -> Result<Value> {
pub async fn admin_regenerate_key(&self, account_id: &str, user_id: &str) -> Result<Value> {
let path = format!(
"/api/v1/admin/accounts/{}/users/{}/key",
account_id, user_id
Expand Down
36 changes: 19 additions & 17 deletions crates/ov_cli/src/commands/admin.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::client::HttpClient;
use crate::error::Result;
use crate::output::{output_success, OutputFormat};
use crate::output::{OutputFormat, output_success};
use serde_json::json;

pub async fn create_account(
Expand All @@ -10,7 +10,9 @@ pub async fn create_account(
output_format: OutputFormat,
compact: bool,
) -> Result<()> {
let response = client.admin_create_account(account_id, admin_user_id).await?;
let response = client
.admin_create_account(account_id, admin_user_id)
.await?;
output_success(&response, output_format, compact);
Ok(())
}
Expand All @@ -32,13 +34,12 @@ pub async fn delete_account(
compact: bool,
) -> Result<()> {
let response = client.admin_delete_account(account_id).await?;
let result = if response.is_null()
|| response.as_object().map(|o| o.is_empty()).unwrap_or(false)
{
json!({"account_id": account_id})
} else {
response
};
let result =
if response.is_null() || response.as_object().map(|o| o.is_empty()).unwrap_or(false) {
json!({"account_id": account_id})
} else {
response
};
output_success(&result, output_format, compact);
Ok(())
}
Expand All @@ -51,7 +52,9 @@ pub async fn register_user(
output_format: OutputFormat,
compact: bool,
) -> Result<()> {
let response = client.admin_register_user(account_id, user_id, role).await?;
let response = client
.admin_register_user(account_id, user_id, role)
.await?;
output_success(&response, output_format, compact);
Ok(())
}
Expand All @@ -75,13 +78,12 @@ pub async fn remove_user(
compact: bool,
) -> Result<()> {
let response = client.admin_remove_user(account_id, user_id).await?;
let result = if response.is_null()
|| response.as_object().map(|o| o.is_empty()).unwrap_or(false)
{
json!({"account_id": account_id, "user_id": user_id})
} else {
response
};
let result =
if response.is_null() || response.as_object().map(|o| o.is_empty()).unwrap_or(false) {
json!({"account_id": account_id, "user_id": user_id})
} else {
response
};
output_success(&result, output_format, compact);
Ok(())
}
Expand Down
33 changes: 24 additions & 9 deletions crates/ov_cli/src/commands/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use std::time::Duration;

use clap::Parser;
use reqwest::Client;
use rustyline::error::ReadlineError;
use rustyline::DefaultEditor;
use rustyline::error::ReadlineError;
use serde::{Deserialize, Serialize};
use termimad::MadSkin;

Expand Down Expand Up @@ -90,7 +90,7 @@ struct ChatResponse {
/// Stream event from SSE
#[derive(Debug, Deserialize)]
struct ChatStreamEvent {
event: String, // "reasoning", "tool_call", "tool_result", "response"
event: String, // "reasoning", "tool_call", "tool_result", "response"
data: serde_json::Value,
timestamp: Option<String>,
}
Expand Down Expand Up @@ -198,7 +198,11 @@ impl ChatCommand {
let mut buffer = String::new();
let mut final_message = String::new();

while let Some(chunk) = response.chunk().await.map_err(|e| Error::Network(format!("Stream error: {}", e)))? {
while let Some(chunk) = response
.chunk()
.await
.map_err(|e| Error::Network(format!("Stream error: {}", e)))?
{
let chunk_str = String::from_utf8_lossy(&chunk);
buffer.push_str(&chunk_str);

Expand All @@ -221,7 +225,8 @@ impl ChatCommand {
} else if let Some(obj) = event.data.as_object() {
if let Some(msg) = obj.get("message").and_then(|m| m.as_str()) {
final_message = msg.to_string();
} else if let Some(err) = obj.get("error").and_then(|e| e.as_str()) {
} else if let Some(err) = obj.get("error").and_then(|e| e.as_str())
{
eprintln!("\x1b[1;31mError: {}\x1b[0m", err);
}
}
Expand Down Expand Up @@ -290,7 +295,10 @@ impl ChatCommand {
}

// Send message
match self.send_interactive_message(client, input, &mut session_id).await {
match self
.send_interactive_message(client, input, &mut session_id)
.await
{
Ok(_) => {}
Err(e) => {
eprintln!("\x1b[1;31mError: {}\x1b[0m", e);
Expand Down Expand Up @@ -330,9 +338,11 @@ impl ChatCommand {
session_id: &mut Option<String>,
) -> Result<()> {
if self.stream {
self.send_interactive_message_stream(client, input, session_id).await
self.send_interactive_message_stream(client, input, session_id)
.await
} else {
self.send_interactive_message_non_stream(client, input, session_id).await
self.send_interactive_message_non_stream(client, input, session_id)
.await
}
}

Expand Down Expand Up @@ -431,7 +441,11 @@ impl ChatCommand {
let mut final_message = String::new();
let mut got_session_id = false;

while let Some(chunk) = response.chunk().await.map_err(|e| Error::Network(format!("Stream error: {}", e)))? {
while let Some(chunk) = response
.chunk()
.await
.map_err(|e| Error::Network(format!("Stream error: {}", e)))?
{
let chunk_str = String::from_utf8_lossy(&chunk);
buffer.push_str(&chunk_str);

Expand Down Expand Up @@ -464,7 +478,8 @@ impl ChatCommand {
} else if let Some(obj) = event.data.as_object() {
if let Some(msg) = obj.get("message").and_then(|m| m.as_str()) {
final_message = msg.to_string();
} else if let Some(err) = obj.get("error").and_then(|e| e.as_str()) {
} else if let Some(err) = obj.get("error").and_then(|e| e.as_str())
{
eprintln!("\x1b[1;31mError: {}\x1b[0m", err);
}
}
Expand Down
13 changes: 5 additions & 8 deletions crates/ov_cli/src/commands/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,14 @@ pub async fn reindex(
Ok(())
}

pub async fn get(
client: &HttpClient,
uri: &str,
local_path: &str,
) -> Result<()> {
pub async fn get(client: &HttpClient, uri: &str, local_path: &str) -> Result<()> {
// Check if target path already exists
let path = Path::new(local_path);
if path.exists() {
return Err(crate::error::Error::Client(
format!("File already exists: {}", local_path)
));
return Err(crate::error::Error::Client(format!(
"File already exists: {}",
local_path
)));
}

// Ensure parent directory exists
Expand Down
Loading