use serde_json::Value;
use crate::canonical::{CanonicalError, ContentKind, Delta, Event, Role};
use crate::protocol::json::{http_error, parse, text_of, u32_at};
use crate::protocol::{DecodeState, Frame, OpenBlock};
mod full;
mod terminal;
pub(super) use full::decode_full;
pub(super) fn decode(frame: Frame, state: &mut DecodeState) -> Result<Vec<Event>, CanonicalError> {
if let Some(status) = frame.status {
return Ok(vec![Event::Error(http_error(&frame.data, status))]); }
Ok(event(&parse(&frame.data)?, state))
}
fn event(v: &Value, state: &mut DecodeState) -> Vec<Event> {
match v["type"].as_str().unwrap_or_default() {
"response.created" | "response.in_progress" => message_start(v, state),
"response.output_item.added" => item_added(v, state),
"response.content_part.added" => part_added(v, state),
"response.output_text.delta" => delta(v, state, Delta::TextDelta),
"response.function_call_arguments.delta" => delta(v, state, Delta::JsonDelta),
"response.reasoning_summary_text.delta" => delta(v, state, Delta::ThinkingDelta),
"response.reasoning_text.delta" => delta(v, state, Delta::ThinkingDelta), "response.refusal.delta" => {
state.refusal.push_str(&text_of(v, "delta")); vec![]
}
"response.output_item.done" => item_done(v, state),
"response.completed" => terminal::completed(v, state),
"response.incomplete" => terminal::incomplete(v, state),
"response.failed" | "response.error" => vec![Event::Error(terminal::stream_error(v))], _ => vec![],
}
}
fn message_start(v: &Value, state: &mut DecodeState) -> Vec<Event> {
if state.started {
return vec![];
}
state.started = true;
let r = &v["response"];
vec![Event::message_start(
r["id"].as_str().map(str::to_owned),
r["model"].as_str().map(str::to_owned),
Role::Assistant,
)]
}
fn item_added(v: &Value, state: &mut DecodeState) -> Vec<Event> {
let item = &v["item"];
let kind = match item["type"].as_str() {
Some("function_call") => ContentKind::ToolUse {
id: text_of(item, "call_id"),
name: text_of(item, "name"),
},
Some("reasoning") => ContentKind::Thinking {
id: item["id"].as_str().map(str::to_owned),
},
_ => return vec![],
};
let index = canonical(state, part_key(v)); open(state, index, kind.clone());
vec![Event::ContentStart { index, kind }]
}
fn part_added(v: &Value, state: &mut DecodeState) -> Vec<Event> {
if v["part"]["type"].as_str() != Some("output_text") {
return vec![];
}
let index = canonical(state, part_key(v));
open(state, index, ContentKind::Text {});
vec![Event::ContentStart {
index,
kind: ContentKind::Text {},
}]
}
fn delta(v: &Value, state: &mut DecodeState, wrap: fn(String) -> Delta) -> Vec<Event> {
let Some(&index) = state.part_index.get(&part_key(v)) else {
return vec![]; };
if !state.open.contains_key(&index) {
return vec![]; }
let frag = text_of(v, "delta");
vec![Event::ContentDelta {
index,
delta: wrap(frag),
}]
}
fn item_done(v: &Value, state: &mut DecodeState) -> Vec<Event> {
let oi = u32_at(v, "output_index");
let enc = (v["item"]["type"].as_str() == Some("reasoning"))
.then(|| v["item"]["encrypted_content"].as_str())
.flatten();
let mut indices: Vec<u32> = state
.part_index
.iter()
.filter(|((o, _), c)| *o == oi && state.open.contains_key(c))
.map(|(_, &c)| c)
.collect();
indices.sort_unstable();
let mut out = Vec::new();
for index in indices {
state.open.remove(&index);
if let Some(e) = enc {
out.push(Event::ContentDelta {
index,
delta: Delta::EncryptedReasoningDelta(e.to_owned()),
});
}
out.push(Event::ContentStop { index });
}
out
}
fn open(state: &mut DecodeState, index: u32, kind: ContentKind) {
state.open.insert(index, OpenBlock { kind });
}
fn canonical(state: &mut DecodeState, key: (u32, u32)) -> u32 {
let next = state.part_index.len() as u32;
*state.part_index.entry(key).or_insert(next)
}
fn part_key(v: &Value) -> (u32, u32) {
(u32_at(v, "output_index"), u32_at(v, "content_index"))
}