anveesa 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Cargo.lock CHANGED
@@ -54,7 +54,7 @@ dependencies = [
54
54
 
55
55
  [[package]]
56
56
  name = "anveesa"
57
- version = "0.3.0"
57
+ version = "0.3.1"
58
58
  dependencies = [
59
59
  "anyhow",
60
60
  "base64",
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "anveesa"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  edition = "2024"
5
5
  default-run = "anveesa"
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anveesa",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "A terminal CLI that wraps AI providers (OpenAI-compatible APIs and local CLIs) into a single unified command",
5
5
  "main": "bin/anveesa.js",
6
6
  "bin": {
@@ -105,7 +105,10 @@ pub async fn ask(
105
105
  usage_requested = false;
106
106
  continue;
107
107
  }
108
- bail!("provider '{provider_name}' returned HTTP {status}: {response_body}");
108
+ bail!(
109
+ "provider '{provider_name}' HTTP {status}: {}",
110
+ extract_api_error(&response_body)
111
+ );
109
112
  }
110
113
 
111
114
  let mut state = StreamState::default();
@@ -1017,6 +1020,49 @@ fn is_stream_options_error(body: &str) -> bool {
1017
1020
  lower.contains("stream_options") || lower.contains("include_usage")
1018
1021
  }
1019
1022
 
1023
+ /// Extract a concise, human-readable error message from a provider HTTP error body.
1024
+ /// Parses `{"error":{"message":"..."}}`, strips verbose class prefixes (e.g. litellm.*),
1025
+ /// takes only the first line, and truncates to 120 chars.
1026
+ fn extract_api_error(body: &str) -> String {
1027
+ // Try to pull error.message out of the JSON
1028
+ let extracted = serde_json::from_str::<Value>(body)
1029
+ .ok()
1030
+ .and_then(|v| {
1031
+ v.pointer("/error/message")
1032
+ .or_else(|| v.get("message"))
1033
+ .and_then(|m| m.as_str())
1034
+ .map(str::to_string)
1035
+ });
1036
+
1037
+ let raw = extracted.as_deref().unwrap_or(body);
1038
+
1039
+ // First line only
1040
+ let line = raw.lines().next().unwrap_or(raw).trim();
1041
+
1042
+ // Strip a leading "Namespace.ErrorClass: " or "ClassName: " prefix once
1043
+ // (covers litellm.BadRequestError, OpenAIException, etc.)
1044
+ let stripped = if let Some(colon) = line.find(": ") {
1045
+ let prefix = &line[..colon];
1046
+ if !prefix.is_empty()
1047
+ && prefix
1048
+ .chars()
1049
+ .all(|c| c.is_alphanumeric() || c == '.' || c == '_')
1050
+ {
1051
+ line[colon + 2..].trim_start()
1052
+ } else {
1053
+ line
1054
+ }
1055
+ } else {
1056
+ line
1057
+ };
1058
+
1059
+ if stripped.len() > 120 {
1060
+ format!("{}…", &stripped[..120])
1061
+ } else {
1062
+ stripped.to_string()
1063
+ }
1064
+ }
1065
+
1020
1066
  #[cfg(test)]
1021
1067
  mod tests {
1022
1068
  use super::*;