malvin 0.2.5

Non-interactive research and coding agent
use serde_json::Value;

use crate::bridge_protocol::BridgeEvent;

pub(super) fn usage_event(params: &Value) -> Vec<BridgeEvent> {
    usage_from_codex(params)
        .map(|usage| BridgeEvent::Usage { usage })
        .into_iter()
        .collect()
}

pub(super) fn usage_from_turn(value: &Value) -> Option<Value> {
    usage_from_codex(value.get("params").unwrap_or(value))
}

fn usage_from_codex(params: &Value) -> Option<Value> {
    let last = usage_object(params)?;
    let mut out = serde_json::Map::new();
    copy_num(last, &mut out, "inputTokens", "inputTokens");
    copy_num(last, &mut out, "outputTokens", "outputTokens");
    copy_num(last, &mut out, "cachedInputTokens", "cacheReadTokens");
    copy_num(last, &mut out, "cacheWriteInputTokens", "cacheWriteTokens");
    copy_num(last, &mut out, "reasoningOutputTokens", "reasoningTokens");
    copy_num(last, &mut out, "totalTokens", "totalTokens");
    copy_num(last, &mut out, "cacheReadTokens", "cacheReadTokens");
    copy_num(last, &mut out, "cacheWriteTokens", "cacheWriteTokens");
    copy_num(last, &mut out, "reasoningTokens", "reasoningTokens");
    (!out.is_empty()).then_some(Value::Object(out))
}

fn usage_object(params: &Value) -> Option<&serde_json::Map<String, Value>> {
    params
        .pointer("/tokenUsage/last")
        .or_else(|| params.pointer("/turn/tokenUsage/last"))
        .or_else(|| params.pointer("/tokenUsage"))
        .or_else(|| params.pointer("/turn/tokenUsage"))
        .or_else(|| params.get("last"))
        .and_then(Value::as_object)
}

fn copy_num(
    src: &serde_json::Map<String, Value>,
    dest: &mut serde_json::Map<String, Value>,
    from: &str,
    to: &str,
) {
    if let Some(v) = src.get(from)
        && (v.as_u64().is_some() || v.as_i64().is_some() || v.as_f64().is_some())
    {
        dest.insert(to.to_string(), v.clone());
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn kiss_cov_map_event_usage() {
        let _ = stringify!(usage_event);
        let _ = stringify!(usage_from_turn);
        let _ = stringify!(usage_from_codex);
        let _ = stringify!(usage_object);
        let _ = stringify!(copy_num);
    }
}