Merge remote-tracking branch 'origin/main' into merge-origin-main-20260420b

# Conflicts:
#	lib/crates/fabro-cli/src/commands/doctor.rs
#	lib/crates/fabro-cli/src/commands/model.rs
#	lib/crates/fabro-cli/src/commands/provider/login.rs
#	lib/crates/fabro-cli/src/commands/repo/init.rs
#	lib/crates/fabro-cli/src/commands/secret/list.rs
#	lib/crates/fabro-cli/src/commands/secret/rm.rs
#	lib/crates/fabro-cli/src/commands/secret/set.rs
#	lib/crates/fabro-cli/src/commands/system/df.rs
#	lib/crates/fabro-cli/src/commands/system/events.rs
#	lib/crates/fabro-cli/src/commands/system/info.rs
#	lib/crates/fabro-cli/src/commands/system/prune.rs
#	lib/crates/fabro-cli/src/commands/version.rs
#	lib/crates/fabro-cli/src/server_client.rs
#	lib/crates/fabro-server/src/csp.rs
This commit is contained in:
Bryan Helmkamp 2026-04-20 14:25:20 -04:00
commit 42aeffeb6b
No known key found for this signature in database
30 changed files with 312 additions and 320 deletions

View file

@ -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};

View file

@ -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<Arc<ServerStoreClient>>,
server: OnceCell<Arc<Client>>,
}
impl CommandContext {
@ -135,7 +135,7 @@ impl CommandContext {
&self.cli_settings
}
pub(crate) async fn server(&self) -> Result<Arc<ServerStoreClient>> {
pub(crate) async fn server(&self) -> Result<Arc<Client>> {
let server_mode = self.server_mode.clone();
let base_config_path = self.base_config_path.clone();
let machine_settings = self.machine_settings.clone();

View file

@ -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,

View file

@ -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<ArtifactEntry>)> {
) -> Result<(RunId, Client, Vec<ArtifactEntry>)> {
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;

View file

@ -132,7 +132,7 @@ pub(super) async fn login_command(
#[cfg(unix)]
async fn fetch_cli_auth_config(target: &ServerTarget) -> Result<types::CliAuthConfig> {
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()

View file

@ -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));

View file

@ -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<fabro_api::Client>>,
connect_server: impl for<'a> Fn(&'a Path) -> BoxFuture<'a, Result<server_client::Client>>,
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<PendingSettingsWrite<'_>>,
server_was_running: bool,
connect_api_client: impl for<'a> Fn(&'a Path) -> BoxFuture<'a, Result<fabro_api::Client>>,
connect_server: impl for<'a> Fn(&'a Path) -> BoxFuture<'a, Result<server_client::Client>>,
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) })
},
{

View file

@ -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<TInput, TOutput>(value: TInput) -> Result<TOutput>
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<Vec<Model>> {
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<Model>>(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<api_types::ModelTestMode>,
) -> Result<api_types::ModelTestResult> {
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());
}
}

View file

@ -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!(

View file

@ -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!(

View file

@ -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<EventEnvelope>,
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;

View file

@ -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,

View file

@ -152,7 +152,7 @@ fn try_parse_relative_duration(s: &str) -> Option<chrono::Duration> {
}
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<bool> {
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,

View file

@ -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<Option<String>> {
@ -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<Option<String>> {
@ -342,7 +342,7 @@ fn blob_id_from_response(response: &str) -> Option<RunBlobId> {
}
async fn list_artifact_display_entries_with_client(
client: &server_client::ServerStoreClient,
client: &server_client::Client,
run_id: &RunId,
) -> Result<Vec<(String, u32, String)>> {
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,

View file

@ -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<TimelineEntry
}
async fn reset_rewound_run_state(
client: &ServerStoreClient,
client: &Client,
git_store: &Store,
run_id: &fabro_types::RunId,
entry: &TimelineEntry,

View file

@ -239,7 +239,7 @@ async fn apply_worker_control_line(
fn build_artifact_uploader(
run_id: RunId,
client: server_client::ServerStoreClient,
client: server_client::Client,
artifact_upload_token: Option<String>,
) -> Arc<dyn StageArtifactUploader> {
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<Mutex<RunProjection>>,
events: Arc<Mutex<Option<Vec<EventEnvelope>>>>,
}
impl HttpRunStore {
async fn connect(
run_id: RunId,
client: server_client::ServerStoreClient,
) -> Result<RunStoreHandle> {
async fn connect(run_id: RunId, client: server_client::Client) -> Result<RunStoreHandle> {
let state = client
.get_run_state(&run_id)
.await

View file

@ -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<()> {

View file

@ -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<()> {

View file

@ -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<()> {

View file

@ -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<Utc>, now: DateTime<Utc>) -> String {
@ -23,15 +23,12 @@ fn format_age(dt: DateTime<Utc>, now: DateTime<Utc>) -> 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(());

View file

@ -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 {

View file

@ -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<String> {
}
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 {

View file

@ -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 }
}
}

View file

@ -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)

View file

@ -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;

View file

@ -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)?;

View file

@ -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)
}

View file

@ -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(),

View file

@ -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<Mutex<()>>,
}
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<String>,
}
@ -153,7 +156,7 @@ fn client_bundle(
http_client: fabro_http::HttpClient,
bearer_token: Option<String>,
) -> 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<ServerStoreClient> {
connect_api_client_bundle(storage_dir).await
}
@ -256,7 +258,11 @@ async fn connect_api_client_bundle(storage_dir: &Path) -> Result<ServerStoreClie
connect_local_api_client_bundle(storage_dir, &user_config::active_settings_path(None)).await
}
pub(crate) async fn connect_api_client(storage_dir: &Path) -> Result<fabro_api::Client> {
#[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<fabro_api::ApiClient> {
connect_api_client_bundle(storage_dir)
.await
.map(|client| client.client_bundle().client)
@ -576,7 +582,7 @@ impl ServerStoreClient {
request: F,
) -> Result<progenitor_client::ResponseValue<T>>
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<T>,
@ -833,6 +839,165 @@ impl ServerStoreClient {
.map_err(|err| anyhow!("invalid run ID from server: {err}"))
}
pub(crate) async fn list_secrets(&self) -> Result<Vec<types::SecretMetadata>> {
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<types::SecretMetadata> {
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<Vec<Model>> {
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<Model>>(parsed.data)?);
if !parsed.meta.has_more {
break;
}
offset += count;
}
Ok(models)
}
pub(crate) async fn test_model(
&self,
id: &str,
mode: Option<types::ModelTestMode>,
) -> Result<types::ModelTestResult> {
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<progenitor_client::ByteStream> {
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<types::SystemInfoResponse> {
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<types::DiskUsageResponse> {
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<types::PruneRunsResponse> {
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<types::DiagnosticsReport> {
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<types::RepoCheckResponse> {
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,

View file

@ -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<ServerStoreClient>,
client: Arc<Client>,
runs: Vec<ServerRunSummaryInfo>,
}
impl ServerSummaryLookup {
pub(crate) async fn from_client(client: Arc<ServerStoreClient>) -> Result<Self> {
pub(crate) async fn from_client(client: Arc<Client>) -> Result<Self> {
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()
}