@@ -6,10 +6,16 @@ use reqwest::Client;
66use serde:: { Deserialize , Serialize } ;
77use serde_json:: { Value , json} ;
88use std:: pin:: Pin ;
9+ #[ cfg( feature = "claude-cli" ) ]
910use std:: process:: Stdio ;
11+ #[ cfg( feature = "claude-cli" ) ]
1012use std:: sync:: Mutex as StdMutex ;
13+ #[ cfg( feature = "claude-cli" ) ]
1114use tokio:: io:: { AsyncBufReadExt , BufReader } ;
15+ #[ cfg( feature = "claude-cli" ) ]
1216use tracing:: { debug, info} ;
17+ #[ cfg( not( feature = "claude-cli" ) ) ]
18+ use tracing:: debug;
1319
1420use crate :: config:: Config ;
1521
@@ -216,6 +222,7 @@ fn normalize_model_id(provider: &str, model_id: &str) -> String {
216222}
217223
218224pub fn create_provider ( model : & str , config : & Config ) -> Result < Box < dyn LLMProvider > > {
225+ #[ cfg( feature = "claude-cli" ) ]
219226 let workspace = config. workspace_path ( ) ;
220227
221228 // Resolve aliases first (e.g., "opus" → "anthropic/claude-opus-4-5")
@@ -279,13 +286,21 @@ pub fn create_provider(model: &str, config: &Config) -> Result<Box<dyn LLMProvid
279286 ) ?) )
280287 }
281288
289+ #[ cfg( feature = "claude-cli" ) ]
282290 "claude-cli" => {
283291 let cli_config = config. providers . claude_cli . as_ref ( ) ;
284292 let command = cli_config. map ( |c| c. command . as_str ( ) ) . unwrap_or ( "claude" ) ;
285293 Ok ( Box :: new ( ClaudeCliProvider :: new (
286294 command, & model_id, workspace,
287295 ) ?) )
288296 }
297+ #[ cfg( not( feature = "claude-cli" ) ) ]
298+ "claude-cli" => {
299+ anyhow:: bail!(
300+ "Claude CLI provider is not available in this build.\n \
301+ The 'claude-cli' feature is required for subprocess-based providers."
302+ )
303+ }
289304
290305 "ollama" => {
291306 let ollama_config = config. providers . ollama . as_ref ( ) . ok_or_else ( || {
@@ -322,6 +337,7 @@ pub fn create_provider(model: &str, config: &Config) -> Result<Box<dyn LLMProvid
322337
323338 _ => {
324339 // Fallback: try Claude CLI if configured
340+ #[ cfg( feature = "claude-cli" ) ]
325341 if let Some ( cli_config) = & config. providers . claude_cli {
326342 return Ok ( Box :: new ( ClaudeCliProvider :: new (
327343 & cli_config. command ,
@@ -1258,6 +1274,7 @@ impl LLMProvider for OllamaProvider {
12581274 }
12591275}
12601276
1277+ #[ cfg( feature = "claude-cli" ) ]
12611278/// Claude CLI Provider - invokes the `claude` CLI command
12621279/// No tool support (text in → text out only)
12631280/// No streaming (CLI output is collected then returned)
@@ -1274,9 +1291,11 @@ pub struct ClaudeCliProvider {
12741291 cli_session_id : StdMutex < Option < String > > ,
12751292}
12761293
1294+ #[ cfg( feature = "claude-cli" ) ]
12771295/// Provider name for CLI session storage
12781296const CLAUDE_CLI_PROVIDER : & str = "claude-cli" ;
12791297
1298+ #[ cfg( feature = "claude-cli" ) ]
12801299impl ClaudeCliProvider {
12811300 pub fn new ( command : & str , model : & str , workspace : std:: path:: PathBuf ) -> Result < Self > {
12821301 // Load existing CLI session from session store
@@ -1452,6 +1471,7 @@ impl ClaudeCliProvider {
14521471 }
14531472}
14541473
1474+ #[ cfg( feature = "claude-cli" ) ]
14551475/// Load CLI session ID from session store
14561476fn load_cli_session_from_store ( session_key : & str , provider : & str ) -> Option < String > {
14571477 use super :: session_store:: SessionStore ;
@@ -1460,6 +1480,7 @@ fn load_cli_session_from_store(session_key: &str, provider: &str) -> Option<Stri
14601480 store. get_cli_session_id ( session_key, provider)
14611481}
14621482
1483+ #[ cfg( feature = "claude-cli" ) ]
14631484/// Save CLI session ID to session store
14641485fn save_cli_session_to_store (
14651486 session_key : & str ,
@@ -1474,6 +1495,7 @@ fn save_cli_session_to_store(
14741495 Ok ( ( ) )
14751496}
14761497
1498+ #[ cfg( feature = "claude-cli" ) ]
14771499fn normalize_claude_model ( model : & str ) -> String {
14781500 match model. to_lowercase ( ) . as_str ( ) {
14791501 "opus" | "opus-4.5" | "opus-4" | "claude-opus-4-5" => "opus" ,
@@ -1484,6 +1506,7 @@ fn normalize_claude_model(model: &str) -> String {
14841506 . to_string ( )
14851507}
14861508
1509+ #[ cfg( feature = "claude-cli" ) ]
14871510/// Check if a message is the synthetic security block appended by `messages_for_api_call`.
14881511fn is_security_block ( msg : & Message ) -> bool {
14891512 msg. role == Role :: User
@@ -1492,6 +1515,7 @@ fn is_security_block(msg: &Message) -> bool {
14921515 . contains ( crate :: security:: HARDCODED_SECURITY_SUFFIX )
14931516}
14941517
1518+ #[ cfg( feature = "claude-cli" ) ]
14951519fn build_prompt_from_messages ( messages : & [ Message ] ) -> String {
14961520 // Get the last *real* user message as the prompt, skipping the security block
14971521 messages
@@ -1502,6 +1526,7 @@ fn build_prompt_from_messages(messages: &[Message]) -> String {
15021526 . unwrap_or_default ( )
15031527}
15041528
1529+ #[ cfg( feature = "claude-cli" ) ]
15051530fn extract_system_prompt ( messages : & [ Message ] ) -> Option < String > {
15061531 let system = messages
15071532 . iter ( )
@@ -1524,6 +1549,7 @@ fn extract_system_prompt(messages: &[Message]) -> Option<String> {
15241549 }
15251550}
15261551
1552+ #[ cfg( feature = "claude-cli" ) ]
15271553/// Parse Claude CLI JSON output, returning (response_text, session_id)
15281554fn parse_claude_cli_output ( stdout : & str ) -> Result < ( String , Option < String > ) > {
15291555 // Claude CLI outputs JSON with message content and session info
@@ -1553,6 +1579,7 @@ fn parse_claude_cli_output(stdout: &str) -> Result<(String, Option<String>)> {
15531579 Ok ( ( stdout. trim ( ) . to_string ( ) , None ) )
15541580}
15551581
1582+ #[ cfg( feature = "claude-cli" ) ]
15561583#[ async_trait]
15571584impl LLMProvider for ClaudeCliProvider {
15581585 fn reset_session ( & self ) {
0 commit comments