use anyhow::{anyhow, Result};
use brotli;
use flate2::read::{DeflateDecoder, GzDecoder};
use rquest::Response;
use serde::Deserialize;
use std::io::Read;
use chrono::Local;
use lazy_static::lazy_static;
use regex::Regex;
use serde_json;
pub async fn decode_body(res: Response) -> Result<Vec<u8>> {
let encoding = res
.headers()
.get("Content-Encoding")
.and_then(|v| v.to_str().ok())
.unwrap_or("")
.to_string();
let bytes_result = res.bytes().await;
let buf = match bytes_result {
Ok(bytes) if bytes.is_empty() => {
return Err(anyhow!("Empty response body received from server"))
}
Ok(bytes) => bytes.to_vec(),
Err(e) => return Err(anyhow!("Failed to read response body: {}", e)),
};
let out = match encoding.as_str() {
"gzip" => {
let mut d = GzDecoder::new(&buf[..]);
let mut v = Vec::new();
d.read_to_end(&mut v)?;
v
}
"deflate" => {
let mut d = DeflateDecoder::new(&buf[..]);
let mut v = Vec::new();
d.read_to_end(&mut v)?;
v
}
"br" => {
let mut v = Vec::new();
brotli::Decompressor::new(&buf[..], 4096).read_to_end(&mut v)?;
v
}
_ => buf,
};
Ok(out)
}
#[derive(Debug, Deserialize)]
struct SendLine {
#[serde(default)]
completion: String,
#[serde(default)]
error: Option<ClaudeErrorInfo>,
}
#[derive(Debug, Deserialize)]
struct ClaudeErrorInfo {
#[serde(default)]
#[allow(non_snake_case)]
r#type: String,
#[serde(default)]
message: String,
#[serde(default)]
resets_at: Option<i64>,
}
pub fn parse_stream(bytes: &[u8]) -> Result<String> {
let text = String::from_utf8_lossy(bytes);
let mut completions = String::new();
for line in text.lines() {
if let Some(start) = line.find('{') {
if let Ok(obj) = serde_json::from_str::<SendLine>(&line[start..]) {
if let Some(err) = obj.error {
match err.r#type.as_str() {
"message_rate_limit_error" => {
let secs = err.resets_at.unwrap_or_default() - Local::now().timestamp();
return Err(anyhow!(
"Rate-limit: wait {} seconds – {}",
secs,
err.message
));
}
"overloaded_error" => {
return Err(anyhow!("Server overloaded: {}", err.message));
}
_ => return Err(anyhow!("Claude error: {}", err.message)),
}
}
completions.push_str(&obj.completion);
}
}
}
Ok(completions.trim().to_owned())
}
lazy_static! {
pub static ref READ_RE: Regex = Regex::new(r"(?i)^#?\s*read_file\s+(.+)").unwrap();
pub static ref EXEC_RE: Regex = Regex::new(r"(?i)^#?\s*exec\s+(.+)").unwrap();
}
pub fn format_tool_line(line: &str) -> String {
if READ_RE.is_match(line) {
format!("\x1b[34m{}\x1b[0m", line.trim_start_matches('#').trim())
} else if EXEC_RE.is_match(line) {
format!(
"\x1b[38;5;208m{}\x1b[0m",
line.trim_start_matches('#').trim()
)
} else {
line.to_owned()
}
}
pub fn extract_commands(answer: &str) -> (Vec<String>, Vec<String>) {
let mut reads = Vec::new();
let mut execs = Vec::new();
let mut lines = answer.lines().peekable();
while lines.peek().is_some() {
let line = lines.next().unwrap();
if let Some(caps) = READ_RE.captures(line) {
reads.extend(caps[1].split_whitespace().map(|s| s.to_string()));
} else if let Some(caps) = EXEC_RE.captures(line) {
let first = caps[1].to_string();
if let Some(hd) = first.split("<<").nth(1) {
let delim = hd.trim().trim_matches(|c| c == '\'' || c == '"');
let mut block = vec![first.clone()];
for l in lines.by_ref() {
block.push(l.to_string());
if l.trim() == delim {
break;
}
}
execs.push(block.join("\n"));
} else {
execs.push(first);
}
}
}
(reads, execs)
}
pub fn prettify(answer: &str) -> String {
answer
.lines()
.map(format_tool_line)
.collect::<Vec<_>>()
.join("\n")
}