Gate --mode and --server-url CLI args behind server feature

When compiled without the server feature, these args are useless since
there is no server to delegate to. Wraps the CLI fields, parser,
resolve_mode, build_server_client, and related tests in cfg(feature =
"server"), with standalone-only fallback paths in each command handler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-10 19:16:59 -04:00
parent f9d114c521
commit fc77a2fb24
2 changed files with 114 additions and 65 deletions

View file

@ -1,7 +1,9 @@
pub use arc_config::cli::*;
#[cfg(feature = "server")]
use tracing::debug;
#[cfg(feature = "server")]
#[derive(Debug, PartialEq)]
pub struct ResolvedMode {
pub mode: ExecutionMode,
@ -9,8 +11,10 @@ pub struct ResolvedMode {
pub tls: Option<ClientTlsConfig>,
}
#[cfg(feature = "server")]
const DEFAULT_SERVER_URL: &str = "http://localhost:3000";
#[cfg(feature = "server")]
pub fn resolve_mode(
cli_mode: Option<ExecutionMode>,
cli_server_url: Option<&str>,
@ -36,6 +40,7 @@ pub fn resolve_mode(
}
}
#[cfg(feature = "server")]
pub fn build_server_client(tls: Option<&ClientTlsConfig>) -> anyhow::Result<reqwest::Client> {
let Some(tls) = tls else {
return Ok(reqwest::Client::new());
@ -65,7 +70,7 @@ pub fn build_server_client(tls: Option<&ClientTlsConfig>) -> anyhow::Result<reqw
Ok(client)
}
#[cfg(test)]
#[cfg(all(test, feature = "server"))]
mod tests {
use std::path::PathBuf;

View file

@ -20,10 +20,12 @@ struct Cli {
debug: bool,
/// Execution mode: standalone (in-process) or server (delegate to API)
#[cfg(feature = "server")]
#[arg(long, global = true, value_parser = parse_execution_mode)]
mode: Option<cli_config::ExecutionMode>,
/// Server URL (overrides server.base_url from cli.toml)
#[cfg(feature = "server")]
#[arg(long, global = true)]
server_url: Option<String>,
@ -31,6 +33,7 @@ struct Cli {
command: Command,
}
#[cfg(feature = "server")]
fn parse_execution_mode(s: &str) -> Result<cli_config::ExecutionMode, String> {
match s {
"standalone" => Ok(cli_config::ExecutionMode::Standalone),
@ -237,41 +240,63 @@ async fn main_inner() -> Result<()> {
if args.model.is_none() {
args.model = llm_defaults.and_then(|l| l.model.clone());
}
let resolved =
cli_config::resolve_mode(cli.mode, cli.server_url.as_deref(), &cli_config);
match resolved.mode {
cli_config::ExecutionMode::Server => {
let client = cli_config::build_server_client(resolved.tls.as_ref())?;
let server = arc_llm::cli::ServerConnection {
client,
base_url: resolved.server_base_url,
};
arc_llm::cli::run_prompt_via_server(args, &server).await?
}
cli_config::ExecutionMode::Standalone => {
arc_llm::cli::run_prompt(args).await?
#[cfg(feature = "server")]
{
let resolved = cli_config::resolve_mode(
cli.mode,
cli.server_url.as_deref(),
&cli_config,
);
match resolved.mode {
cli_config::ExecutionMode::Server => {
let client =
cli_config::build_server_client(resolved.tls.as_ref())?;
let server = arc_llm::cli::ServerConnection {
client,
base_url: resolved.server_base_url,
};
arc_llm::cli::run_prompt_via_server(args, &server).await?
}
cli_config::ExecutionMode::Standalone => {
arc_llm::cli::run_prompt(args).await?
}
}
}
#[cfg(not(feature = "server"))]
{
arc_llm::cli::run_prompt(args).await?
}
}
LlmCommand::Chat(mut args) => {
if args.model.is_none() {
args.model = llm_defaults.and_then(|l| l.model.clone());
}
let resolved =
cli_config::resolve_mode(cli.mode, cli.server_url.as_deref(), &cli_config);
match resolved.mode {
cli_config::ExecutionMode::Server => {
let client = cli_config::build_server_client(resolved.tls.as_ref())?;
let server = arc_llm::cli::ServerConnection {
client,
base_url: resolved.server_base_url,
};
arc_llm::cli::run_chat_via_server(args, &server).await?
}
cli_config::ExecutionMode::Standalone => {
arc_llm::cli::run_chat(args).await?
#[cfg(feature = "server")]
{
let resolved = cli_config::resolve_mode(
cli.mode,
cli.server_url.as_deref(),
&cli_config,
);
match resolved.mode {
cli_config::ExecutionMode::Server => {
let client =
cli_config::build_server_client(resolved.tls.as_ref())?;
let server = arc_llm::cli::ServerConnection {
client,
base_url: resolved.server_base_url,
};
arc_llm::cli::run_chat_via_server(args, &server).await?
}
cli_config::ExecutionMode::Standalone => {
arc_llm::cli::run_chat(args).await?
}
}
}
#[cfg(not(feature = "server"))]
{
arc_llm::cli::run_chat(args).await?
}
}
}
}
@ -284,6 +309,7 @@ async fn main_inner() -> Result<()> {
exec_defaults.and_then(|a| a.permissions),
exec_defaults.and_then(|a| a.output_format),
);
#[cfg(feature = "server")]
let resolved =
cli_config::resolve_mode(cli.mode, cli.server_url.as_deref(), &cli_config);
let mcp_servers: Vec<arc_mcp::config::McpServerConfig> = cli_config
@ -291,35 +317,44 @@ async fn main_inner() -> Result<()> {
.into_iter()
.map(|(name, entry)| entry.into_config(name))
.collect();
match resolved.mode {
cli_config::ExecutionMode::Server => {
tracing::info!(mode = "server", "Agent session starting");
let http_client = cli_config::build_server_client(resolved.tls.as_ref())?;
let provider_name = args
.provider
.clone()
.unwrap_or_else(|| "anthropic".to_string());
let adapter = std::sync::Arc::new(arc_llm::providers::ArcServerAdapter::new(
http_client,
&resolved.server_base_url,
&provider_name,
));
let mut client = arc_llm::client::Client::new(
std::collections::HashMap::new(),
None,
vec![],
);
client.register_provider(adapter).await.map_err(|e| {
anyhow::anyhow!("Failed to register arc server adapter: {e}")
})?;
arc_agent::cli::run_with_args_and_client(args, Some(client), mcp_servers)
.await?
}
cli_config::ExecutionMode::Standalone => {
tracing::info!(mode = "standalone", "Agent session starting");
arc_agent::cli::run_with_args(args, mcp_servers).await?
#[cfg(feature = "server")]
{
match resolved.mode {
cli_config::ExecutionMode::Server => {
tracing::info!(mode = "server", "Agent session starting");
let http_client = cli_config::build_server_client(resolved.tls.as_ref())?;
let provider_name = args
.provider
.clone()
.unwrap_or_else(|| "anthropic".to_string());
let adapter =
std::sync::Arc::new(arc_llm::providers::ArcServerAdapter::new(
http_client,
&resolved.server_base_url,
&provider_name,
));
let mut client = arc_llm::client::Client::new(
std::collections::HashMap::new(),
None,
vec![],
);
client.register_provider(adapter).await.map_err(|e| {
anyhow::anyhow!("Failed to register arc server adapter: {e}")
})?;
arc_agent::cli::run_with_args_and_client(args, Some(client), mcp_servers)
.await?
}
cli_config::ExecutionMode::Standalone => {
tracing::info!(mode = "standalone", "Agent session starting");
arc_agent::cli::run_with_args(args, mcp_servers).await?
}
}
}
#[cfg(not(feature = "server"))]
{
tracing::info!(mode = "standalone", "Agent session starting");
arc_agent::cli::run_with_args(args, mcp_servers).await?
}
}
Command::Run(mut args) => {
let styles: &'static arc_util::terminal::Styles =
@ -362,18 +397,27 @@ async fn main_inner() -> Result<()> {
arc_workflows::cli::diff::diff_command(args).await?;
}
Command::Model { command } => {
let cli_config = cli_config::load_cli_config(None)?;
let resolved =
cli_config::resolve_mode(cli.mode, cli.server_url.as_deref(), &cli_config);
let server = match resolved.mode {
cli_config::ExecutionMode::Server => {
let client = cli_config::build_server_client(resolved.tls.as_ref())?;
Some(arc_llm::cli::ServerConnection {
client,
base_url: resolved.server_base_url,
})
let server = {
#[cfg(feature = "server")]
{
let cli_config = cli_config::load_cli_config(None)?;
let resolved =
cli_config::resolve_mode(cli.mode, cli.server_url.as_deref(), &cli_config);
match resolved.mode {
cli_config::ExecutionMode::Server => {
let client = cli_config::build_server_client(resolved.tls.as_ref())?;
Some(arc_llm::cli::ServerConnection {
client,
base_url: resolved.server_base_url,
})
}
cli_config::ExecutionMode::Standalone => None,
}
}
#[cfg(not(feature = "server"))]
{
None
}
cli_config::ExecutionMode::Standalone => None,
};
arc_llm::cli::run_models(command, server).await?
}