use serde_json::Value;
use crate::canonical::{CanonicalError, ErrorKind, Event};
use crate::protocol::anthropic::AnthropicMessages;
use crate::protocol::json::{parse, to_json_string};
use crate::protocol::{DecodeState, Frame, Protocol};
pub(super) fn decode(frame: Frame, state: &mut DecodeState) -> Result<Vec<Event>, CanonicalError> {
if frame.status.is_some() {
return AnthropicMessages.decode(frame, state);
}
let v: Value = parse(&frame.data)?;
match v["type"].as_str().unwrap_or_default() {
"stream_event" => AnthropicMessages.decode(
Frame {
event: None,
data: to_json_string(&v["event"]).into_bytes(),
status: None,
},
state,
),
"assistant" => {
if let Some(tag) = v["error"].as_str() {
state.error_tag = Some(tag.to_owned());
}
Ok(vec![])
}
"result" => Ok(result_events(&v, state)),
_ => Ok(vec![]),
}
}
fn result_events(v: &Value, state: &mut DecodeState) -> Vec<Event> {
let had_message = state.terminated;
state.terminated = true;
if v["is_error"].as_bool() == Some(true) {
return vec![Event::Error(result_error(v, state))];
}
if had_message {
return vec![];
}
vec![Event::Error(CanonicalError {
kind: ErrorKind::Transport,
message: "claude exited without a message stream (result with no completion)".to_owned(),
provider_detail: Some(v.clone()),
retry_after_seconds: None,
})]
}
fn result_error(v: &Value, state: &DecodeState) -> CanonicalError {
let kind = match v["api_error_status"].as_u64() {
Some(status) => ErrorKind::from_http_status(status as u16),
None if state.error_tag.as_deref() == Some("authentication_failed") => ErrorKind::Auth,
None => ErrorKind::Transport,
};
let message = match v["result"].as_str() {
Some(text) if !text.is_empty() => text.to_owned(),
_ => "claude exited with an error".to_owned(),
};
CanonicalError {
kind,
message,
provider_detail: Some(v.clone()),
retry_after_seconds: None,
}
}
pub(super) fn decode_full(
body: &[u8],
state: &mut DecodeState,
) -> Result<Vec<Event>, CanonicalError> {
let mut out = Vec::new();
for line in body.split(|b| *b == b'\n') {
if line.iter().all(u8::is_ascii_whitespace) {
continue;
}
out.extend(decode(
Frame {
event: None,
data: line.to_vec(),
status: None,
},
state,
)?);
}
Ok(out)
}