use serde_json::Value;
use crate::canonical::{CanonicalError, ErrorKind, Event, FinishReason, Usage};
use crate::protocol::json::text_of;
use crate::protocol::DecodeState;
pub(super) fn completed(v: &Value, state: &mut DecodeState) -> Vec<Event> {
let response = &v["response"];
let reason = completed_finish(response, &state.refusal);
terminal(response, reason, state)
}
pub(super) fn incomplete(v: &Value, state: &mut DecodeState) -> Vec<Event> {
let response = &v["response"];
let r = response["incomplete_details"]["reason"]
.as_str()
.unwrap_or_default();
let reason = if r == "max_output_tokens" {
FinishReason::Length
} else {
FinishReason::Other(r.to_owned())
};
terminal(response, reason, state)
}
fn terminal(response: &Value, reason: FinishReason, state: &mut DecodeState) -> Vec<Event> {
let mut out = Vec::new();
let mut open: Vec<u32> = state.open.keys().copied().collect();
open.sort_unstable();
for index in open {
state.open.remove(&index);
out.push(Event::ContentStop { index });
}
if let Some(u) = usage(response) {
out.push(Event::Usage(u));
}
out.push(Event::Finish { reason });
state.terminated = true;
out
}
fn completed_finish(response: &Value, refusal: &str) -> FinishReason {
if !refusal.is_empty() {
return FinishReason::Refusal {
category: "refusal".into(),
explanation: Some(refusal.to_owned()),
};
}
let has_tool = response["output"]
.as_array()
.into_iter()
.flatten()
.any(|i| i["type"].as_str() == Some("function_call"));
if has_tool {
return FinishReason::ToolUse;
}
match response["status"].as_str() {
None | Some("completed") => FinishReason::Stop,
Some(other) => FinishReason::Other(other.to_owned()),
}
}
fn usage(response: &Value) -> Option<Usage> {
let u = response.get("usage").filter(|u| u.is_object())?;
Some(Usage {
input_tokens: u["input_tokens"].as_u64().map(|x| x as u32),
output_tokens: u["output_tokens"].as_u64().map(|x| x as u32),
cache_read_tokens: u["input_tokens_details"]["cached_tokens"]
.as_u64()
.map(|x| x as u32),
cache_write_tokens: None,
})
}
pub(super) fn stream_error(v: &Value) -> CanonicalError {
let err = if v["response"]["error"].is_object() {
v["response"]["error"].clone()
} else {
v["error"].clone()
};
CanonicalError {
kind: stream_error_kind(&err),
message: text_of(&err, "message"),
provider_detail: Some(err),
retry_after_seconds: None,
}
}
fn stream_error_kind(err: &Value) -> ErrorKind {
let tag = err["code"]
.as_str()
.or_else(|| err["type"].as_str())
.unwrap_or_default();
match tag {
"server_error" => ErrorKind::Provider { status: 500 },
"rate_limit_exceeded" | "rate_limit_error" => ErrorKind::Provider { status: 429 },
_ => ErrorKind::Transport,
}
}