use serde::Deserialize;
#[derive(Debug, Clone, Default, Deserialize)]
pub struct CopilotInput {
pub session_id: Option<String>,
#[serde(default)]
pub context_window: ContextWindow,
#[serde(default)]
pub ai_used: AiUsed,
#[allow(dead_code)]
pub model: Option<ModelInfo>,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct ContextWindow {
#[serde(default)]
pub current_context_tokens: Option<u64>,
#[serde(default)]
pub displayed_context_limit: Option<u64>,
pub current_context_used_percentage: Option<f64>,
#[serde(default)]
pub total_input_tokens: Option<u64>,
#[serde(default)]
pub total_output_tokens: Option<u64>,
#[serde(default)]
pub total_cache_read_tokens: Option<u64>,
#[serde(default)]
pub total_cache_write_tokens: Option<u64>,
#[serde(default)]
pub total_reasoning_tokens: Option<u64>,
#[serde(default)]
pub total_tokens: Option<u64>,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct AiUsed {
#[serde(default)]
pub total_nano_aiu: u64,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct ModelInfo {
#[allow(dead_code)]
pub id: Option<String>,
#[allow(dead_code)]
pub name: Option<String>,
}
impl CopilotInput {
pub fn from_json(json_str: &str) -> Self {
serde_json::from_str(json_str).unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_full_payload() {
let json = r#"{
"session_id": "sess-123",
"context_window": {
"current_context_tokens": 1250,
"displayed_context_limit": 200000,
"current_context_used_percentage": 0.6,
"total_input_tokens": 50000,
"total_output_tokens": 12000,
"total_cache_read_tokens": 35000,
"total_cache_write_tokens": 8000,
"total_reasoning_tokens": 3200,
"total_tokens": 62000
},
"ai_used": {
"total_nano_aiu": 500000000000
},
"model": {
"id": "claude-3-7-sonnet",
"name": "Claude 3.7 Sonnet"
}
}"#;
let input = CopilotInput::from_json(json);
assert_eq!(input.session_id.as_deref(), Some("sess-123"));
assert_eq!(input.context_window.current_context_tokens, Some(1250));
assert_eq!(input.context_window.displayed_context_limit, Some(200000));
assert_eq!(
input.context_window.current_context_used_percentage,
Some(0.6)
);
assert_eq!(input.context_window.total_cache_read_tokens, Some(35000));
assert_eq!(input.context_window.total_cache_write_tokens, Some(8000));
assert_eq!(input.context_window.total_reasoning_tokens, Some(3200));
assert_eq!(input.context_window.total_tokens, Some(62000));
assert_eq!(input.ai_used.total_nano_aiu, 500000000000);
assert_eq!(
input.model.as_ref().and_then(|m| m.id.as_deref()),
Some("claude-3-7-sonnet")
);
}
#[test]
fn test_parse_empty_payload() {
let input = CopilotInput::from_json("{}");
assert_eq!(input.session_id, None);
assert_eq!(input.context_window.current_context_tokens, None);
assert_eq!(input.ai_used.total_nano_aiu, 0);
assert!(input.model.is_none());
}
#[test]
fn test_parse_invalid_json_falls_back_to_default() {
let input = CopilotInput::from_json("invalid json");
assert_eq!(input.session_id, None);
assert_eq!(input.ai_used.total_nano_aiu, 0);
}
}