1use thiserror::Error;
4
5#[derive(Error, Debug)]
7pub enum TokenError {
8 #[error("Input contains invalid UTF-8 at byte {offset}")]
9 InvalidUtf8 { offset: usize },
10
11 #[error("Unknown model: '{model}'. {suggestion}")]
12 UnknownModel { model: String, suggestion: String },
13
14 #[error("Input size ({size} bytes) exceeds maximum limit ({limit} bytes). Consider processing in smaller chunks.")]
15 InputTooLarge { size: usize, limit: usize },
16
17 #[error("IO error: {0}")]
18 Io(#[from] std::io::Error),
19
20 #[error("Tokenization error: {0}")]
21 Tokenization(String),
22
23 #[error(
25 "Accurate mode requires ANTHROPIC_API_KEY environment variable.\n\n\
26 Get your API key from: https://console.anthropic.com/\n\
27 Then set: export ANTHROPIC_API_KEY=\"sk-ant-...\"\n\n\
28 For offline estimation (no API key needed), omit --accurate flag:\n \
29 token-count --model {model}"
30 )]
31 MissingApiKey { model: String },
32
33 #[error(
35 "Invalid ANTHROPIC_API_KEY. Please check your API key.\n\n\
36 Get a valid key from: https://console.anthropic.com/"
37 )]
38 InvalidApiKey,
39
40 #[error(
42 "Anthropic API rate limit exceeded. Please try again later.\n\n\
43 Rate limits: https://docs.anthropic.com/en/api/rate-limits"
44 )]
45 RateLimited,
46
47 #[error(
49 "Anthropic API server error (HTTP {0}). The service may be temporarily unavailable.\n\n\
50 Check status: https://status.anthropic.com/"
51 )]
52 ApiServerError(u16),
53
54 #[error("Anthropic API error: {0}")]
56 ApiError(String),
57
58 #[error(
60 "API call requires consent. Running in non-interactive mode (stdin not a TTY).\n\n\
61 Options:\n \
62 1. Add -y/--yes flag to skip prompt:\n \
63 cat file.txt | token-count --model {model} --accurate -y\n \n \
64 2. Use estimation mode (no API call):\n \
65 cat file.txt | token-count --model {model}"
66 )]
67 NonInteractiveWithoutYes { model: String },
68}
69
70impl TokenError {
71 pub fn exit_code(&self) -> i32 {
73 match self {
74 Self::InvalidUtf8 { .. } => 1,
75 Self::UnknownModel { .. } => 2,
76 Self::InputTooLarge { .. } => 1,
77 Self::Io(_) => 1,
78 Self::Tokenization(_) => 1,
79 Self::MissingApiKey { .. } => 1,
80 Self::InvalidApiKey => 1,
81 Self::RateLimited => 1,
82 Self::ApiServerError(_) => 1,
83 Self::ApiError(_) => 1,
84 Self::NonInteractiveWithoutYes { .. } => 1,
85 }
86 }
87}