Skip to main content

token_count/api/
consent.rs

1//! Interactive consent prompt for API calls
2//!
3//! This module provides a reusable consent mechanism for prompting users
4//! before sending data to external APIs. It includes TTY detection to
5//! prevent hanging in non-interactive environments.
6
7use crate::error::TokenError;
8use std::io::{self, IsTerminal, Write};
9
10/// Configuration for API consent prompt
11pub struct ConsentPrompt {
12    /// Provider name (e.g., "Anthropic", "OpenAI")
13    pub provider: &'static str,
14
15    /// API endpoint URL (for transparency)
16    pub api_endpoint: &'static str,
17}
18
19impl ConsentPrompt {
20    /// Ask user for consent (interactive mode only)
21    ///
22    /// Returns `Ok(true)` if user consents, `Ok(false)` if user declines.
23    /// Returns `Err` if running in non-interactive mode (stdin not a TTY).
24    pub fn ask(&self) -> Result<bool, TokenError> {
25        // Check if stdin is a TTY (terminal)
26        if !io::stdin().is_terminal() {
27            return Err(TokenError::NonInteractiveWithoutYes {
28                model: "claude".to_string(), // Default model for error message
29            });
30        }
31
32        // Display prompt on stderr (don't pollute stdout)
33        eprintln!();
34        eprintln!(
35            "This will send your input to {}'s API for accurate token counting.",
36            self.provider
37        );
38        eprintln!("Your input will be transmitted over HTTPS to: {}", self.api_endpoint);
39        eprintln!();
40        eprint!("Proceed with API call? (y/N): ");
41
42        // Flush stderr to ensure prompt is visible
43        io::stderr().flush().map_err(|e| {
44            TokenError::Io(io::Error::other(format!("Failed to flush stderr: {}", e)))
45        })?;
46
47        // Read user response from stdin
48        let mut response = String::new();
49        io::stdin().read_line(&mut response).map_err(TokenError::Io)?;
50
51        // Accept: y, Y, yes, YES (case-insensitive)
52        // Reject: n, N, no, NO, empty, anything else
53        let normalized = response.trim().to_lowercase();
54        Ok(normalized == "y" || normalized == "yes")
55    }
56}