diff --git a/lib/crates/fabro-api/src/lib.rs b/lib/crates/fabro-api/src/lib.rs index 86ba4c414..ca2774c83 100644 --- a/lib/crates/fabro-api/src/lib.rs +++ b/lib/crates/fabro-api/src/lib.rs @@ -13,4 +13,4 @@ mod generated { include!(concat!(env!("OUT_DIR"), "/codegen.rs")); } -pub use generated::{Client, types}; +pub use generated::{Client as ApiClient, types}; diff --git a/lib/crates/fabro-cli/src/command_context.rs b/lib/crates/fabro-cli/src/command_context.rs index 7d5bcf0e9..c8f6635b8 100644 --- a/lib/crates/fabro-cli/src/command_context.rs +++ b/lib/crates/fabro-cli/src/command_context.rs @@ -9,7 +9,7 @@ use fabro_util::printer::Printer; use tokio::sync::OnceCell; use crate::args::{ServerConnectionArgs, ServerTargetArgs}; -use crate::server_client::ServerStoreClient; +use crate::server_client::Client; use crate::{server_client, user_config}; #[derive(Clone, Debug)] @@ -35,7 +35,7 @@ pub(crate) struct CommandContext { machine_settings: SettingsLayer, cli_settings: CliSettings, server_mode: ServerMode, - server: OnceCell>, + server: OnceCell>, } impl CommandContext { @@ -135,7 +135,7 @@ impl CommandContext { &self.cli_settings } - pub(crate) async fn server(&self) -> Result> { + pub(crate) async fn server(&self) -> Result> { let server_mode = self.server_mode.clone(); let base_config_path = self.base_config_path.clone(); let machine_settings = self.machine_settings.clone(); diff --git a/lib/crates/fabro-cli/src/commands/artifact/cp.rs b/lib/crates/fabro-cli/src/commands/artifact/cp.rs index 9fa791919..a465af4f7 100644 --- a/lib/crates/fabro-cli/src/commands/artifact/cp.rs +++ b/lib/crates/fabro-cli/src/commands/artifact/cp.rs @@ -11,7 +11,7 @@ use fabro_types::settings::cli::{CliLayer, OutputFormat}; use fabro_util::printer::Printer; use crate::args::ArtifactCpArgs; -use crate::server_client::ServerStoreClient; +use crate::server_client::Client; use crate::shared::{print_json_pretty, split_run_path}; pub(super) async fn cp_command( @@ -140,7 +140,7 @@ pub(super) async fn cp_command( } async fn write_artifact_file( - client: &ServerStoreClient, + client: &Client, run_id: &fabro_types::RunId, entry: &super::ArtifactEntry, dest_file: &Path, diff --git a/lib/crates/fabro-cli/src/commands/artifact/mod.rs b/lib/crates/fabro-cli/src/commands/artifact/mod.rs index 8eae5a937..56cfa6ad8 100644 --- a/lib/crates/fabro-cli/src/commands/artifact/mod.rs +++ b/lib/crates/fabro-cli/src/commands/artifact/mod.rs @@ -9,7 +9,7 @@ use fabro_util::printer::Printer; use crate::args::{ArtifactCommand, ArtifactNamespace, ServerTargetArgs}; use crate::command_context::CommandContext; -use crate::server_client::ServerStoreClient; +use crate::server_client::Client; #[derive(Clone, Debug, serde::Serialize)] pub(super) struct ArtifactEntry { @@ -29,7 +29,7 @@ pub(super) async fn resolve_artifacts( cli: &CliSettings, cli_layer: &CliLayer, printer: Printer, -) -> Result<(RunId, ServerStoreClient, Vec)> { +) -> Result<(RunId, Client, Vec)> { let ctx = CommandContext::for_target(server, printer, cli.clone(), cli_layer)?; let client = ctx.server().await?; let run_id = client.resolve_run(run_selector).await?.run_id; diff --git a/lib/crates/fabro-cli/src/commands/auth/login.rs b/lib/crates/fabro-cli/src/commands/auth/login.rs index 592d396d1..a3a96f85b 100644 --- a/lib/crates/fabro-cli/src/commands/auth/login.rs +++ b/lib/crates/fabro-cli/src/commands/auth/login.rs @@ -132,7 +132,7 @@ pub(super) async fn login_command( #[cfg(unix)] async fn fetch_cli_auth_config(target: &ServerTarget) -> Result { let (http_client, base_url) = user_config::build_public_http_client(target)?; - let client = fabro_api::Client::new_with_client(&base_url, http_client); + let client = fabro_api::ApiClient::new_with_client(&base_url, http_client); client .get_cli_auth_config() .send() diff --git a/lib/crates/fabro-cli/src/commands/doctor.rs b/lib/crates/fabro-cli/src/commands/doctor.rs index b30f03ce0..f7543e41d 100644 --- a/lib/crates/fabro-cli/src/commands/doctor.rs +++ b/lib/crates/fabro-cli/src/commands/doctor.rs @@ -312,10 +312,7 @@ pub(crate) async fn run_doctor( } }; - if let Err(err) = server - .send_api(|client| async move { client.get_health().send().await }) - .await - { + if let Err(err) = server.get_health().await { report.sections.push(CheckSection { title: "Server".to_string(), checks: vec![CheckResult { @@ -352,12 +349,8 @@ pub(crate) async fn run_doctor( }], }); - match server - .send_api(|client| async move { client.run_diagnostics().send().await }) - .await - { - Ok(response) => { - let diagnostics = response.into_inner(); + match server.run_diagnostics().await { + Ok(diagnostics) => { report.sections[0] .checks .push(check_version_parity(&diagnostics.version)); diff --git a/lib/crates/fabro-cli/src/commands/install.rs b/lib/crates/fabro-cli/src/commands/install.rs index 5a21ad30f..5f916a91a 100644 --- a/lib/crates/fabro-cli/src/commands/install.rs +++ b/lib/crates/fabro-cli/src/commands/install.rs @@ -1094,19 +1094,17 @@ async fn setup_github_app( } async fn persist_vault_secrets_via_server( - client: &fabro_api::Client, + client: &server_client::Client, secrets: &[CreateSecretRequest], ) -> Result<()> { for secret in secrets { client - .create_secret() - .body(CreateSecretRequest { + .create_secret(CreateSecretRequest { name: secret.name.clone(), value: secret.value.clone(), type_: secret.type_, description: secret.description.clone(), }) - .send() .await?; } @@ -1117,14 +1115,14 @@ async fn persist_vault_secrets_with( storage_dir: &Path, secrets: &[CreateSecretRequest], server_was_running: bool, - connect_api_client: impl for<'a> Fn(&'a Path) -> BoxFuture<'a, Result>, + connect_server: impl for<'a> Fn(&'a Path) -> BoxFuture<'a, Result>, stop_server: impl for<'a> Fn(&'a Path, Duration) -> BoxFuture<'a, bool>, ) -> Result<()> { if secrets.is_empty() { return Ok(()); } - let client = match connect_api_client(storage_dir).await { + let client = match connect_server(storage_dir).await { Ok(client) => client, Err(err) => { if !server_was_running { @@ -1173,7 +1171,7 @@ async fn persist_install_outputs( vault_secrets, settings_write, server_was_running, - |path| Box::pin(server_client::connect_api_client(path)), + |path| Box::pin(server_client::connect_server(path)), |path, timeout| { Box::pin(async move { stop::stop_server(path, timeout).await.unwrap_or(false) }) }, @@ -1303,7 +1301,7 @@ async fn persist_install_outputs_with_settings( vault_secrets: &[CreateSecretRequest], settings_write: Option>, server_was_running: bool, - connect_api_client: impl for<'a> Fn(&'a Path) -> BoxFuture<'a, Result>, + connect_server: impl for<'a> Fn(&'a Path) -> BoxFuture<'a, Result>, stop_server: impl for<'a> Fn(&'a Path, Duration) -> BoxFuture<'a, bool>, ) -> Result<()> { persist_server_env_secrets(storage_dir, server_env_secrets)?; @@ -1317,7 +1315,7 @@ async fn persist_install_outputs_with_settings( storage_dir, vault_secrets, server_was_running, - connect_api_client, + connect_server, stop_server, ) .await; @@ -2486,10 +2484,7 @@ client_id = "client-id" &vault_secrets, false, |_| { - let client = fabro_api::Client::new_with_client( - &server.base_url(), - fabro_test::test_http_client(), - ); + let client = server_client::Client::new_no_proxy(&server.base_url()).unwrap(); Box::pin(async move { Ok(client) }) }, { @@ -2548,10 +2543,7 @@ client_id = "client-id" &vault_secrets, true, |_| { - let client = fabro_api::Client::new_with_client( - &server.base_url(), - fabro_test::test_http_client(), - ); + let client = server_client::Client::new_no_proxy(&server.base_url()).unwrap(); Box::pin(async move { Ok(client) }) }, { diff --git a/lib/crates/fabro-cli/src/commands/model.rs b/lib/crates/fabro-cli/src/commands/model.rs index 673b25409..61b22b490 100644 --- a/lib/crates/fabro-cli/src/commands/model.rs +++ b/lib/crates/fabro-cli/src/commands/model.rs @@ -1,18 +1,17 @@ use anyhow::{Context, Result, bail}; use cli_table::format::{Border, Justify, Separator}; use cli_table::{Cell, CellStruct, Color, Style, Table}; -use fabro_api::{self, types as api_types}; +use fabro_api::types as api_types; use fabro_model::{Catalog, Model, Provider}; use fabro_types::settings::CliSettings; use fabro_types::settings::cli::{CliLayer, OutputFormat}; use fabro_util::printer::Printer; use fabro_util::terminal::Styles; use serde::Serialize; -use serde::de::DeserializeOwned; use crate::args::{ModelListArgs, ModelTestArgs, ModelsCommand}; use crate::command_context::CommandContext; -use crate::server_client::ServerStoreClient; +use crate::server_client; #[derive(Serialize)] #[serde(rename_all = "snake_case")] @@ -193,71 +192,13 @@ fn model_test_row_from_status(model: &Model, status: &str, result_color: Color) } } -fn convert_type(value: TInput) -> Result -where - TInput: serde::Serialize, - TOutput: DeserializeOwned, -{ - serde_json::from_value(serde_json::to_value(value)?).map_err(Into::into) -} - -async fn fetch_models_from_server( - client: &ServerStoreClient, - provider: Option<&str>, - query: Option<&str>, -) -> Result> { - let mut offset = 0u64; - let mut models = Vec::new(); - - loop { - let response = client - .send_api(|api| async move { - let mut request = api.list_models().page_limit(100u64).page_offset(offset); - if let Some(provider) = provider { - request = request.provider(provider.to_string()); - } - if let Some(query) = query { - request = request.query(query.to_string()); - } - request.send().await - }) - .await?; - let parsed = response.into_inner(); - let count = parsed.data.len() as u64; - models.extend(convert_type::<_, Vec>(parsed.data)?); - if !parsed.meta.has_more { - break; - } - offset += count; - } - - Ok(models) -} - -async fn test_model_via_server( - client: &ServerStoreClient, - model_id: &str, - mode: Option, -) -> Result { - let response = client - .send_api(|api| async move { - let mut request = api.test_model().id(model_id.to_string()); - if let Some(mode) = mode { - request = request.mode(mode); - } - request.send().await - }) - .await?; - Ok(response.into_inner()) -} - #[allow( clippy::print_stdout, clippy::print_stderr, reason = "Progress goes to stderr while tables or JSON results go to stdout." )] async fn test_models_via_server( - client: &ServerStoreClient, + client: &server_client::Client, provider: Option<&str>, model: Option<&str>, deep: bool, @@ -279,7 +220,7 @@ async fn test_models_via_server( if !json_output { eprint!("Testing {model_id}..."); } - let result = test_model_via_server(client, model_id, request_mode).await; + let result = client.test_model(model_id, request_mode).await; if !json_output { eprintln!(" done"); } @@ -329,7 +270,7 @@ async fn test_models_via_server( rows.push(row); json_rows.push(model_test_row_from_status(&info, &status, result_color)); } else { - let models_to_test = fetch_models_from_server(client, provider, None).await?; + let models_to_test = client.list_models(provider, None).await?; if models_to_test.is_empty() { bail!("No models found"); } @@ -338,7 +279,7 @@ async fn test_models_via_server( if !json_output { eprint!("Testing {}...", info.id); } - let result = test_model_via_server(client, &info.id, request_mode).await; + let result = client.test_model(&info.id, request_mode).await; if !json_output { eprintln!(" done"); } @@ -434,7 +375,7 @@ async fn test_models_via_server( )] async fn run_models( command: ModelsCommand, - client: &ServerStoreClient, + client: &server_client::Client, json_output: bool, ) -> Result<()> { let styles = Styles::detect_stdout(); @@ -443,8 +384,9 @@ async fn run_models( ModelsCommand::List(ModelListArgs { provider, query, .. }) => { - let models = - fetch_models_from_server(client, provider.as_deref(), query.as_deref()).await?; + let models = client + .list_models(provider.as_deref(), query.as_deref()) + .await?; if json_output { println!("{}", serde_json::to_string_pretty(&models)?); @@ -485,8 +427,8 @@ mod tests { use super::*; - fn test_api_client(api_url: &str) -> ServerStoreClient { - ServerStoreClient::new_no_proxy(api_url).expect("test API client should build") + fn test_client(api_url: &str) -> server_client::Client { + server_client::Client::new_no_proxy(api_url).unwrap() } fn test_model_json(id: &str, provider: Provider) -> serde_json::Value { @@ -592,10 +534,8 @@ mod tests { }) .await; - let client = test_api_client(&server.url("")); - let response = test_model_via_server(&client, "test-model", None) - .await - .unwrap(); + let client = test_client(&server.url("")); + let response = client.test_model("test-model", None).await.unwrap(); assert_eq!(response.status, api_types::ModelTestResultStatus::Ok); assert!(response.error_message.is_none()); @@ -622,11 +562,11 @@ mod tests { }) .await; - let client = test_api_client(&server.url("")); - let response = - test_model_via_server(&client, "test-model", Some(api_types::ModelTestMode::Deep)) - .await - .unwrap(); + let client = test_client(&server.url("")); + let response = client + .test_model("test-model", Some(api_types::ModelTestMode::Deep)) + .await + .unwrap(); assert_eq!(response.status, api_types::ModelTestResultStatus::Error); assert_eq!(response.error_message.as_deref(), Some("timeout")); @@ -650,10 +590,8 @@ mod tests { }) .await; - let client = test_api_client(&server.url("")); - let response = test_model_via_server(&client, "kimi-k2.5", None) - .await - .unwrap(); + let client = test_client(&server.url("")); + let response = client.test_model("kimi-k2.5", None).await.unwrap(); assert_eq!(response.status, api_types::ModelTestResultStatus::Skip); assert!(response.error_message.is_none()); @@ -676,8 +614,8 @@ mod tests { }) .await; - let client = test_api_client(&server.url("")); - let result = test_model_via_server(&client, "bad-model", None).await; + let client = test_client(&server.url("")); + let result = client.test_model("bad-model", None).await; assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("Model not found")); } @@ -703,8 +641,8 @@ mod tests { }) .await; - let client = test_api_client(&server.url("")); - let models = fetch_models_from_server(&client, None, None).await.unwrap(); + let client = test_client(&server.url("")); + let models = client.list_models(None, None).await.unwrap(); mock.assert_async().await; assert_eq!(models.len(), 1); @@ -734,10 +672,8 @@ mod tests { }) .await; - let client = test_api_client(&server.url("")); - let models = fetch_models_from_server(&client, Some("anthropic"), None) - .await - .unwrap(); + let client = test_client(&server.url("")); + let models = client.list_models(Some("anthropic"), None).await.unwrap(); assert_eq!(models.len(), 1); assert_eq!(models[0].id, "model-a"); @@ -765,10 +701,8 @@ mod tests { }) .await; - let client = test_api_client(&server.url("")); - let models = fetch_models_from_server(&client, None, Some("sonnet")) - .await - .unwrap(); + let client = test_client(&server.url("")); + let models = client.list_models(None, Some("sonnet")).await.unwrap(); mock.assert_async().await; assert_eq!(models.len(), 1); @@ -813,8 +747,8 @@ mod tests { }) .await; - let client = test_api_client(&server.url("")); - let models = fetch_models_from_server(&client, None, None).await.unwrap(); + let client = test_client(&server.url("")); + let models = client.list_models(None, None).await.unwrap(); first_page.assert_async().await; second_page.assert_async().await; @@ -836,8 +770,8 @@ mod tests { }) .await; - let client = test_api_client(&server.url("")); - let result = fetch_models_from_server(&client, None, None).await; + let client = test_client(&server.url("")); + let result = client.list_models(None, None).await; assert!(result.is_err()); } } diff --git a/lib/crates/fabro-cli/src/commands/provider/login.rs b/lib/crates/fabro-cli/src/commands/provider/login.rs index 46092276f..45b33e9f9 100644 --- a/lib/crates/fabro-cli/src/commands/provider/login.rs +++ b/lib/crates/fabro-cli/src/commands/provider/login.rs @@ -34,21 +34,13 @@ pub(super) async fn login_command( }; let credential_id = credential_id_for(&credential).map_err(anyhow::Error::msg)?; let value = serde_json::to_string(&credential)?; - let request_credential_id = credential_id.clone(); - let request_value = value.clone(); server - .send_api(|client| async move { - client - .create_secret() - .body(types::CreateSecretRequest { - name: request_credential_id.clone(), - value: request_value.clone(), - type_: types::SecretType::Credential, - description: None, - }) - .send() - .await + .create_secret(types::CreateSecretRequest { + name: credential_id.clone(), + value, + type_: types::SecretType::Credential, + description: None, }) .await?; fabro_util::printerr!( diff --git a/lib/crates/fabro-cli/src/commands/repo/init.rs b/lib/crates/fabro-cli/src/commands/repo/init.rs index f867d158b..136eb2bff 100644 --- a/lib/crates/fabro-cli/src/commands/repo/init.rs +++ b/lib/crates/fabro-cli/src/commands/repo/init.rs @@ -222,20 +222,8 @@ async fn check_github_app_installation( } }; - let check_owner = owner.clone(); - let check_repo = repo.clone(); - let check = match server - .send_api(|client| async move { - client - .get_github_repo() - .owner(check_owner.clone()) - .name(check_repo.clone()) - .send() - .await - }) - .await - { - Ok(response) => response.into_inner(), + let check = match server.get_github_repo(&owner, &repo).await { + Ok(response) => response, Err(err) => { fabro_util::printerr!(printer, "\n Warning: could not check GitHub access: {err}"); return; @@ -275,21 +263,8 @@ async fn check_github_app_installation( }) .await; - let recheck_owner = owner.clone(); - let recheck_repo = repo.clone(); - match server - .send_api(|client| async move { - client - .get_github_repo() - .owner(recheck_owner.clone()) - .name(recheck_repo.clone()) - .send() - .await - }) - .await - { + match server.get_github_repo(&owner, &repo).await { Ok(response) => { - let response = response.into_inner(); if response.accessible { let green = console::Style::new().green(); fabro_util::printerr!( diff --git a/lib/crates/fabro-cli/src/commands/run/attach.rs b/lib/crates/fabro-cli/src/commands/run/attach.rs index 007e14e25..c39fc605c 100644 --- a/lib/crates/fabro-cli/src/commands/run/attach.rs +++ b/lib/crates/fabro-cli/src/commands/run/attach.rs @@ -74,7 +74,7 @@ pub(crate) async fn attach_run( } pub(crate) async fn attach_run_with_client( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &RunId, kill_on_detach: bool, styles: &'static Styles, @@ -152,7 +152,7 @@ fn replay_run_with_client( } async fn attach_live_run_with_client( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &RunId, existing_events: Vec, mut stream: server_client::RunAttachEventStream, @@ -226,7 +226,7 @@ async fn attach_live_run_with_client( } async fn handle_pending_server_interview( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &RunId, auto_approve: bool, progress_ui: &mut run_progress::ProgressUI, @@ -262,7 +262,7 @@ async fn handle_pending_server_interview( } async fn handle_detach_signal( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &RunId, kill_on_detach: bool, printer: Printer, @@ -316,7 +316,7 @@ fn api_question_to_question(question: &types::ApiQuestion) -> Question { } async fn submit_server_interview_answer( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &RunId, qid: &str, answer: &fabro_interview::Answer, @@ -625,7 +625,7 @@ mod tests { .header("Content-Type", "application/json") .body(terminal_run_state_response().to_string()); }); - let client = server_client::ServerStoreClient::new_no_proxy(&server.base_url()).unwrap(); + let client = server_client::Client::new_no_proxy(&server.base_url()).unwrap(); handle_detach_signal(&client, &run_id, true, Printer::Default).await; diff --git a/lib/crates/fabro-cli/src/commands/run/cp.rs b/lib/crates/fabro-cli/src/commands/run/cp.rs index b02426ca4..732312219 100644 --- a/lib/crates/fabro-cli/src/commands/run/cp.rs +++ b/lib/crates/fabro-cli/src/commands/run/cp.rs @@ -9,7 +9,7 @@ use tracing::{debug, info}; use crate::args::{CpArgs, ServerTargetArgs}; use crate::command_context::CommandContext; -use crate::server_client::ServerStoreClient; +use crate::server_client::Client; use crate::shared::{print_json_pretty, split_run_path}; #[derive(Debug)] @@ -131,7 +131,7 @@ async fn resolve_client_and_run_id( cli: &CliSettings, cli_layer: &CliLayer, printer: Printer, -) -> Result<(ServerStoreClient, fabro_types::RunId)> { +) -> Result<(Client, fabro_types::RunId)> { let ctx = CommandContext::for_target(server, printer, cli.clone(), cli_layer)?; let client = ctx.server().await?; let run_id = client.resolve_run(run_prefix).await?.run_id; @@ -139,7 +139,7 @@ async fn resolve_client_and_run_id( } async fn write_sandbox_file( - client: &ServerStoreClient, + client: &Client, run_id: &fabro_types::RunId, remote_path: &str, local_path: &Path, @@ -157,7 +157,7 @@ async fn write_sandbox_file( } async fn upload_sandbox_file( - client: &ServerStoreClient, + client: &Client, run_id: &fabro_types::RunId, local_path: &Path, remote_path: &str, @@ -169,7 +169,7 @@ async fn upload_sandbox_file( } async fn download_recursive( - client: &ServerStoreClient, + client: &Client, run_id: &fabro_types::RunId, remote_path: &str, local_path: &Path, @@ -194,7 +194,7 @@ async fn download_recursive( } async fn upload_recursive( - client: &ServerStoreClient, + client: &Client, run_id: &fabro_types::RunId, local_path: &Path, remote_path: &str, diff --git a/lib/crates/fabro-cli/src/commands/run/logs.rs b/lib/crates/fabro-cli/src/commands/run/logs.rs index 6ce41b8fb..dae03d121 100644 --- a/lib/crates/fabro-cli/src/commands/run/logs.rs +++ b/lib/crates/fabro-cli/src/commands/run/logs.rs @@ -152,7 +152,7 @@ fn try_parse_relative_duration(s: &str) -> Option { } async fn follow_store_logs( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &fabro_types::RunId, seq: u32, pretty: bool, @@ -225,7 +225,7 @@ async fn follow_store_logs( } async fn run_concluded( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &fabro_types::RunId, ) -> Result { let state = client @@ -239,7 +239,7 @@ async fn run_concluded( } async fn flush_remaining_store_events( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &fabro_types::RunId, next_seq: u32, pretty: bool, diff --git a/lib/crates/fabro-cli/src/commands/run/output.rs b/lib/crates/fabro-cli/src/commands/run/output.rs index 95383e294..7e89a13d7 100644 --- a/lib/crates/fabro-cli/src/commands/run/output.rs +++ b/lib/crates/fabro-cli/src/commands/run/output.rs @@ -131,7 +131,7 @@ pub(crate) fn api_check_report_to_local(report: &types::PreflightCheckReport) -> } pub(crate) async fn print_run_summary_with_client( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &fabro_types::RunId, local_run_dir: Option<&Path>, styles: &Styles, @@ -292,7 +292,7 @@ pub(crate) fn print_final_output(output: Option<&str>, styles: &Styles, printer: } async fn resolve_final_output_with_client( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &RunId, checkpoint: Option<&fabro_types::Checkpoint>, ) -> Result> { @@ -317,7 +317,7 @@ async fn resolve_final_output_with_client( } async fn resolve_response_string( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &RunId, response: &str, ) -> Result> { @@ -342,7 +342,7 @@ fn blob_id_from_response(response: &str) -> Option { } async fn list_artifact_display_entries_with_client( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &RunId, ) -> Result> { let mut entries = Vec::new(); @@ -356,7 +356,7 @@ async fn list_artifact_display_entries_with_client( } async fn print_assets_with_client( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &RunId, styles: &Styles, printer: Printer, diff --git a/lib/crates/fabro-cli/src/commands/run/rewind.rs b/lib/crates/fabro-cli/src/commands/run/rewind.rs index b0423f949..9486b6756 100644 --- a/lib/crates/fabro-cli/src/commands/run/rewind.rs +++ b/lib/crates/fabro-cli/src/commands/run/rewind.rs @@ -18,7 +18,7 @@ use serde::Serialize; use crate::args::RewindArgs; use crate::command_context::CommandContext; use crate::commands::store::rebuild::rebuild_run_store; -use crate::server_client::ServerStoreClient; +use crate::server_client::Client; use crate::shared::repo::ensure_matching_repo_origin; use crate::shared::{color_if, print_json_pretty}; @@ -111,7 +111,7 @@ pub(crate) fn timeline_entries_json(timeline: &RunTimeline) -> Vec, ) -> Arc { match artifact_upload_token { @@ -254,7 +254,7 @@ fn build_artifact_uploader( struct HttpArtifactUploader { run_id: RunId, - client: server_client::ServerStoreClient, + client: server_client::Client, bearer_token: String, } @@ -318,16 +318,13 @@ impl StageArtifactUploader for MissingArtifactUploadTokenUploader { #[derive(Clone)] struct HttpRunStore { run_id: RunId, - client: server_client::ServerStoreClient, + client: server_client::Client, state: Arc>, events: Arc>>>, } impl HttpRunStore { - async fn connect( - run_id: RunId, - client: server_client::ServerStoreClient, - ) -> Result { + async fn connect(run_id: RunId, client: server_client::Client) -> Result { let state = client .get_run_state(&run_id) .await diff --git a/lib/crates/fabro-cli/src/commands/run/start.rs b/lib/crates/fabro-cli/src/commands/run/start.rs index 1b8e7c46b..0af737d81 100644 --- a/lib/crates/fabro-cli/src/commands/run/start.rs +++ b/lib/crates/fabro-cli/src/commands/run/start.rs @@ -4,7 +4,7 @@ use fabro_types::RunId; use crate::server_client; pub(crate) async fn start_run_with_client( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &RunId, resume: bool, ) -> Result<()> { diff --git a/lib/crates/fabro-cli/src/commands/runs/archive.rs b/lib/crates/fabro-cli/src/commands/runs/archive.rs index 1673b858a..4a0627759 100644 --- a/lib/crates/fabro-cli/src/commands/runs/archive.rs +++ b/lib/crates/fabro-cli/src/commands/runs/archive.rs @@ -65,7 +65,7 @@ impl Action { async fn run_bulk( action: Action, identifiers: &[String], - client: &server_client::ServerStoreClient, + client: &server_client::Client, cli: &CliSettings, printer: Printer, ) -> Result<()> { diff --git a/lib/crates/fabro-cli/src/commands/runs/rm.rs b/lib/crates/fabro-cli/src/commands/runs/rm.rs index 8ea688a5d..becdc9969 100644 --- a/lib/crates/fabro-cli/src/commands/runs/rm.rs +++ b/lib/crates/fabro-cli/src/commands/runs/rm.rs @@ -21,7 +21,7 @@ pub(crate) async fn remove_command( async fn remove_from( args: &RunsRemoveArgs, - client: &server_client::ServerStoreClient, + client: &server_client::Client, cli: &CliSettings, printer: Printer, ) -> Result<()> { @@ -83,7 +83,7 @@ async fn remove_from( } async fn delete_server_run( - client: &server_client::ServerStoreClient, + client: &server_client::Client, run_id: &fabro_types::RunId, force: bool, ) -> Result<()> { diff --git a/lib/crates/fabro-cli/src/commands/secret/list.rs b/lib/crates/fabro-cli/src/commands/secret/list.rs index 2f6f5c28a..033afdf17 100644 --- a/lib/crates/fabro-cli/src/commands/secret/list.rs +++ b/lib/crates/fabro-cli/src/commands/secret/list.rs @@ -8,7 +8,7 @@ use fabro_util::printer::Printer; use fabro_util::terminal::Styles; use crate::args::SecretListArgs; -use crate::server_client::ServerStoreClient; +use crate::server_client::Client; use crate::shared::print_json_pretty; fn format_age(dt: DateTime, now: DateTime) -> String { @@ -23,15 +23,12 @@ fn format_age(dt: DateTime, now: DateTime) -> String { } pub(super) async fn list_command( - client: &ServerStoreClient, + client: &Client, _args: &SecretListArgs, cli: &CliSettings, printer: Printer, ) -> Result<()> { - let response = client - .send_api(|api| async move { api.list_secrets().send().await }) - .await?; - let secrets = response.into_inner().data; + let secrets = client.list_secrets().await?; if cli.output.format == OutputFormat::Json { print_json_pretty(&secrets)?; return Ok(()); diff --git a/lib/crates/fabro-cli/src/commands/secret/rm.rs b/lib/crates/fabro-cli/src/commands/secret/rm.rs index 87220f363..43c466501 100644 --- a/lib/crates/fabro-cli/src/commands/secret/rm.rs +++ b/lib/crates/fabro-cli/src/commands/secret/rm.rs @@ -1,29 +1,19 @@ use anyhow::Result; -use fabro_api::types; use fabro_types::settings::CliSettings; use fabro_types::settings::cli::OutputFormat; use fabro_util::printer::Printer; use crate::args::SecretRmArgs; -use crate::server_client::ServerStoreClient; +use crate::server_client::Client; use crate::shared::print_json_pretty; pub(super) async fn rm_command( - client: &ServerStoreClient, + client: &Client, args: &SecretRmArgs, cli: &CliSettings, printer: Printer, ) -> Result<()> { - client - .send_api(|api| async move { - api.delete_secret_by_name() - .body(types::DeleteSecretRequest { - name: args.key.clone(), - }) - .send() - .await - }) - .await?; + client.delete_secret_by_name(&args.key).await?; if cli.output.format == OutputFormat::Json { print_json_pretty(&serde_json::json!({ "key": args.key }))?; } else { diff --git a/lib/crates/fabro-cli/src/commands/secret/set.rs b/lib/crates/fabro-cli/src/commands/secret/set.rs index 423004d56..a8c88debd 100644 --- a/lib/crates/fabro-cli/src/commands/secret/set.rs +++ b/lib/crates/fabro-cli/src/commands/secret/set.rs @@ -17,7 +17,7 @@ use fabro_util::printer::Printer; use tokio::task::spawn_blocking; use crate::args::{SecretSetArgs, SecretTypeArg}; -use crate::server_client::ServerStoreClient; +use crate::server_client::Client; use crate::shared::print_json_pretty; use crate::shared::provider_auth::prompt_password; @@ -58,26 +58,20 @@ async fn resolve_value(args: &SecretSetArgs) -> Result { } pub(super) async fn set_command( - client: &ServerStoreClient, + client: &Client, args: &SecretSetArgs, cli: &CliSettings, printer: Printer, ) -> Result<()> { let value = resolve_value(args).await?; let meta = client - .send_api(|api| async move { - api.create_secret() - .body(types::CreateSecretRequest { - name: args.key.clone(), - value: value.clone(), - type_: api_secret_type(args.r#type), - description: args.description.clone(), - }) - .send() - .await + .create_secret(types::CreateSecretRequest { + name: args.key.clone(), + value, + type_: api_secret_type(args.r#type), + description: args.description.clone(), }) - .await? - .into_inner(); + .await?; if cli.output.format == OutputFormat::Json { print_json_pretty(&meta)?; } else { diff --git a/lib/crates/fabro-cli/src/commands/store/dump.rs b/lib/crates/fabro-cli/src/commands/store/dump.rs index 3dc495c99..9cd3cc2cd 100644 --- a/lib/crates/fabro-cli/src/commands/store/dump.rs +++ b/lib/crates/fabro-cli/src/commands/store/dump.rs @@ -23,7 +23,7 @@ use tokio::task::spawn_blocking; use super::run_export::StoreRunExport; use crate::args::StoreDumpArgs; use crate::command_context::CommandContext; -use crate::server_client::ServerStoreClient; +use crate::server_client::Client; use crate::shared::{absolute_or_current, print_json_pretty}; pub(crate) async fn dump_command( @@ -164,12 +164,12 @@ impl DumpDataSource for LocalDumpSource<'_> { } struct ServerDumpSource<'a> { - client: &'a ServerStoreClient, + client: &'a Client, run_id: &'a RunId, } impl<'a> ServerDumpSource<'a> { - fn new(client: &'a ServerStoreClient, run_id: &'a RunId) -> Self { + fn new(client: &'a Client, run_id: &'a RunId) -> Self { Self { client, run_id } } } diff --git a/lib/crates/fabro-cli/src/commands/system/df.rs b/lib/crates/fabro-cli/src/commands/system/df.rs index ef1b637f1..ae6e30343 100644 --- a/lib/crates/fabro-cli/src/commands/system/df.rs +++ b/lib/crates/fabro-cli/src/commands/system/df.rs @@ -19,26 +19,16 @@ pub(super) async fn df_command( ) -> Result<()> { let ctx = CommandContext::for_connection(&args.connection, printer, cli.clone(), cli_layer)?; let server = ctx.server().await?; - let output = server - .send_api(|client| async move { - client - .get_system_disk_usage() - .verbose(args.verbose) - .send() - .await - }) - .await? - .into_inner(); - let json = cli.output.format == OutputFormat::Json; - let storage_dir = if json { - None + + let (output, storage_dir) = if json { + (server.get_system_disk_usage(args.verbose).await?, None) } else { - server - .send_api(|client| async move { client.get_system_info().send().await }) - .await? - .into_inner() - .storage_dir + let (output, info) = tokio::try_join!( + server.get_system_disk_usage(args.verbose), + server.get_system_info(), + )?; + (output, info.storage_dir) }; df_from(&output, storage_dir.as_deref(), json) diff --git a/lib/crates/fabro-cli/src/commands/system/events.rs b/lib/crates/fabro-cli/src/commands/system/events.rs index 71041ff70..bba7bebc3 100644 --- a/lib/crates/fabro-cli/src/commands/system/events.rs +++ b/lib/crates/fabro-cli/src/commands/system/events.rs @@ -16,18 +16,7 @@ pub(super) async fn events_command( ) -> Result<()> { let ctx = CommandContext::for_connection(&args.connection, printer, cli.clone(), cli_layer)?; let server = ctx.server().await?; - - let run_ids = args.run_ids.join(","); - let response = server - .send_api(|client| async move { - let mut request = client.attach_events(); - if !run_ids.is_empty() { - request = request.run_id(run_ids.clone()); - } - request.send().await - }) - .await?; - let mut stream = response.into_inner(); + let mut stream = server.attach_events(&args.run_ids).await?; let mut pending = Vec::new(); let json = cli.output.format == OutputFormat::Json; diff --git a/lib/crates/fabro-cli/src/commands/system/info.rs b/lib/crates/fabro-cli/src/commands/system/info.rs index 17b975ee9..17668bfe5 100644 --- a/lib/crates/fabro-cli/src/commands/system/info.rs +++ b/lib/crates/fabro-cli/src/commands/system/info.rs @@ -15,10 +15,7 @@ pub(super) async fn info_command( ) -> Result<()> { let ctx = CommandContext::for_connection(&args.connection, printer, cli.clone(), cli_layer)?; let server = ctx.server().await?; - let response = server - .send_api(|client| async move { client.get_system_info().send().await }) - .await? - .into_inner(); + let response = server.get_system_info().await?; if cli.output.format == OutputFormat::Json { print_json_pretty(&response)?; diff --git a/lib/crates/fabro-cli/src/commands/system/prune.rs b/lib/crates/fabro-cli/src/commands/system/prune.rs index 6ce5b16d6..1ed6d684d 100644 --- a/lib/crates/fabro-cli/src/commands/system/prune.rs +++ b/lib/crates/fabro-cli/src/commands/system/prune.rs @@ -20,22 +20,15 @@ pub(super) async fn prune_command( let ctx = CommandContext::for_connection(&args.connection, printer, cli.clone(), cli_layer)?; let server = ctx.server().await?; let response = server - .send_api(|client| async move { - client - .prune_runs() - .body(types::PruneRunsRequest { - before: args.filter.before.clone(), - dry_run: !args.yes, - labels: parse_label_filters(&args.filter.label), - older_than: args.older_than.map(format_duration), - orphans: args.filter.orphans, - workflow: args.filter.workflow.clone(), - }) - .send() - .await + .prune_runs(types::PruneRunsRequest { + before: args.filter.before.clone(), + dry_run: !args.yes, + labels: parse_label_filters(&args.filter.label), + older_than: args.older_than.map(format_duration), + orphans: args.filter.orphans, + workflow: args.filter.workflow.clone(), }) - .await? - .into_inner(); + .await?; prune_from(&response, cli.output.format == OutputFormat::Json, printer) } diff --git a/lib/crates/fabro-cli/src/commands/version.rs b/lib/crates/fabro-cli/src/commands/version.rs index 09c834272..4a45c2adf 100644 --- a/lib/crates/fabro-cli/src/commands/version.rs +++ b/lib/crates/fabro-cli/src/commands/version.rs @@ -27,23 +27,17 @@ pub(crate) async fn version_command( let server_target = user_config::resolve_server_target(&args.target, ctx.machine_settings())?; let server_address = format_server_target(&server_target); let server_info = match ctx.server().await { - Ok(server) => match server - .send_api(|client| async move { client.get_system_info().send().await }) - .await - { - Ok(response) => { - let response = response.into_inner(); - ServerVersionInfo::Success { - address: server_address, - version: response.version, - git_sha: response.git_sha, - build_date: response.build_date, - profile: response.profile, - os: response.os, - arch: response.arch, - uptime_secs: response.uptime_secs, - } - } + Ok(server) => match server.get_system_info().await { + Ok(response) => ServerVersionInfo::Success { + address: server_address, + version: response.version, + git_sha: response.git_sha, + build_date: response.build_date, + profile: response.profile, + os: response.os, + arch: response.arch, + uptime_secs: response.uptime_secs, + }, Err(err) => ServerVersionInfo::Error { address: server_address, error: err.to_string(), diff --git a/lib/crates/fabro-cli/src/server_client.rs b/lib/crates/fabro-cli/src/server_client.rs index 0fc99ed47..22ac50a4b 100644 --- a/lib/crates/fabro-cli/src/server_client.rs +++ b/lib/crates/fabro-cli/src/server_client.rs @@ -10,6 +10,7 @@ use fabro_api::types; use fabro_config::Storage; use fabro_http::header::{AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE}; use fabro_http::multipart::{Form, Part}; +use fabro_model::Model; use fabro_server::bind::Bind; use fabro_store::{EventEnvelope, RunSummary, StageId}; use fabro_types::settings::SettingsLayer; @@ -40,9 +41,11 @@ pub(crate) struct ServerStoreClient { refresh_lock: Arc>, } +pub(crate) type Client = ServerStoreClient; + #[derive(Clone)] struct ClientBundle { - client: fabro_api::Client, + client: fabro_api::ApiClient, http_client: fabro_http::HttpClient, bearer_token: Option, } @@ -153,7 +156,7 @@ fn client_bundle( http_client: fabro_http::HttpClient, bearer_token: Option, ) -> ClientBundle { - let client = fabro_api::Client::new_with_client(base_url, http_client.clone()); + let client = fabro_api::ApiClient::new_with_client(base_url, http_client.clone()); ClientBundle { client, http_client, @@ -175,7 +178,6 @@ fn refreshable_oauth( Ok(None) } -#[cfg(test)] pub(crate) async fn connect_server(storage_dir: &Path) -> Result { connect_api_client_bundle(storage_dir).await } @@ -256,7 +258,11 @@ async fn connect_api_client_bundle(storage_dir: &Path) -> Result Result { +#[allow( + dead_code, + reason = "Retained for pending storage-backed internal callers and referenced in existing design docs." +)] +pub(crate) async fn connect_api_client(storage_dir: &Path) -> Result { connect_api_client_bundle(storage_dir) .await .map(|client| client.client_bundle().client) @@ -576,7 +582,7 @@ impl ServerStoreClient { request: F, ) -> Result> where - F: FnOnce(fabro_api::Client) -> Fut + Clone, + F: FnOnce(fabro_api::ApiClient) -> Fut + Clone, Fut: std::future::Future< Output = std::result::Result< progenitor_client::ResponseValue, @@ -833,6 +839,165 @@ impl ServerStoreClient { .map_err(|err| anyhow!("invalid run ID from server: {err}")) } + pub(crate) async fn list_secrets(&self) -> Result> { + let response = self + .send_api(|client| async move { client.list_secrets().send().await }) + .await?; + Ok(response.into_inner().data) + } + + pub(crate) async fn create_secret( + &self, + body: types::CreateSecretRequest, + ) -> Result { + let response = self + .send_api( + |client| async move { client.create_secret().body(body.clone()).send().await }, + ) + .await?; + Ok(response.into_inner()) + } + + pub(crate) async fn delete_secret_by_name(&self, name: &str) -> Result<()> { + self.send_api(|client| async move { + client + .delete_secret_by_name() + .body(types::DeleteSecretRequest { + name: name.to_string(), + }) + .send() + .await + }) + .await?; + Ok(()) + } + + pub(crate) async fn list_models( + &self, + provider: Option<&str>, + query: Option<&str>, + ) -> Result> { + let mut offset = 0u64; + let mut models = Vec::new(); + + loop { + let response = self + .send_api(|client| async move { + let mut request = client.list_models().page_limit(100u64).page_offset(offset); + if let Some(provider) = provider { + request = request.provider(provider.to_string()); + } + if let Some(query) = query { + request = request.query(query.to_string()); + } + request.send().await + }) + .await?; + let parsed = response.into_inner(); + let count = parsed.data.len() as u64; + models.extend(convert_type::<_, Vec>(parsed.data)?); + if !parsed.meta.has_more { + break; + } + offset += count; + } + + Ok(models) + } + + pub(crate) async fn test_model( + &self, + id: &str, + mode: Option, + ) -> Result { + let response = self + .send_api(|client| async move { + let mut request = client.test_model().id(id.to_string()); + if let Some(mode) = mode { + request = request.mode(mode); + } + request.send().await + }) + .await?; + Ok(response.into_inner()) + } + + pub(crate) async fn attach_events( + &self, + run_ids: &[String], + ) -> Result { + let response = self + .send_api(|client| async move { + let mut request = client.attach_events(); + if !run_ids.is_empty() { + request = request.run_id(run_ids.join(",")); + } + request.send().await + }) + .await?; + Ok(response.into_inner()) + } + + pub(crate) async fn get_system_info(&self) -> Result { + let response = self + .send_api(|client| async move { client.get_system_info().send().await }) + .await?; + Ok(response.into_inner()) + } + + pub(crate) async fn get_system_disk_usage( + &self, + verbose: bool, + ) -> Result { + let response = self + .send_api(|client| async move { + client.get_system_disk_usage().verbose(verbose).send().await + }) + .await?; + Ok(response.into_inner()) + } + + pub(crate) async fn prune_runs( + &self, + body: types::PruneRunsRequest, + ) -> Result { + let response = self + .send_api(|client| async move { client.prune_runs().body(body.clone()).send().await }) + .await?; + Ok(response.into_inner()) + } + + pub(crate) async fn get_health(&self) -> Result<()> { + self.send_api(|client| async move { client.get_health().send().await }) + .await?; + Ok(()) + } + + pub(crate) async fn run_diagnostics(&self) -> Result { + let response = self + .send_api(|client| async move { client.run_diagnostics().send().await }) + .await?; + Ok(response.into_inner()) + } + + pub(crate) async fn get_github_repo( + &self, + owner: &str, + name: &str, + ) -> Result { + let response = self + .send_api(|client| async move { + client + .get_github_repo() + .owner(owner.to_string()) + .name(name.to_string()) + .send() + .await + }) + .await?; + Ok(response.into_inner()) + } + pub(crate) async fn run_preflight( &self, manifest: types::RunManifest, diff --git a/lib/crates/fabro-cli/src/server_runs.rs b/lib/crates/fabro-cli/src/server_runs.rs index 7a8b9dffa..99010132f 100644 --- a/lib/crates/fabro-cli/src/server_runs.rs +++ b/lib/crates/fabro-cli/src/server_runs.rs @@ -6,7 +6,7 @@ use chrono::{DateTime, Utc}; use fabro_store::RunSummary; use fabro_types::{RunId, RunStatus, StatusReason}; -use crate::server_client::ServerStoreClient; +use crate::server_client::Client; #[derive(Debug, Clone)] pub(crate) struct ServerRunSummaryInfo { @@ -75,12 +75,12 @@ impl ServerRunSummaryInfo { } pub(crate) struct ServerSummaryLookup { - client: Arc, + client: Arc, runs: Vec, } impl ServerSummaryLookup { - pub(crate) async fn from_client(client: Arc) -> Result { + pub(crate) async fn from_client(client: Arc) -> Result { let summaries = client.list_store_runs().await?; let mut runs = summaries .into_iter() @@ -94,7 +94,7 @@ impl ServerSummaryLookup { Ok(Self { client, runs }) } - pub(crate) fn client(&self) -> &ServerStoreClient { + pub(crate) fn client(&self) -> &Client { self.client.as_ref() }