1use serde::{Deserialize, Serialize};
2
3#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
6pub struct ContextPacket {
7 pub id: String,
9 pub source_agent: String,
11 pub created_at: String,
13 pub conversation: Vec<ConversationMessage>,
14 pub code_context: CodeContext,
15 pub artifacts: Vec<ArtifactPayload>,
16}
17
18#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
19pub struct ConversationMessage {
20 pub role: String,
22 pub content: String,
23}
24
25#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
26pub struct CodeContext {
27 pub files: Vec<FileSnapshot>,
28 pub git_diff: Option<String>,
29 pub active_file: Option<String>,
31 pub project_root: Option<String>,
33}
34
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
36pub struct FileSnapshot {
37 pub path: String,
38 pub content: String,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
43pub struct ArtifactPayload {
44 pub name: String,
45 pub content: String,
46 pub media_type: String,
48}
49
50impl ContextPacket {
51 pub fn new(source_agent: impl Into<String>) -> Self {
52 Self {
53 id: uuid::Uuid::new_v4().to_string(),
54 source_agent: source_agent.into(),
55 created_at: chrono_now(),
56 conversation: Vec::new(),
57 code_context: CodeContext::default(),
58 artifacts: Vec::new(),
59 }
60 }
61
62 pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
63 serde_json::to_vec(self)
64 }
65
66 pub fn from_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
67 serde_json::from_slice(bytes)
68 }
69}
70
71fn chrono_now() -> String {
72 use std::time::{SystemTime, UNIX_EPOCH};
74 let secs = SystemTime::now()
75 .duration_since(UNIX_EPOCH)
76 .map(|d| d.as_secs())
77 .unwrap_or(0);
78 format!("{secs}")
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn round_trip_serialization() {
87 let mut pkt = ContextPacket::new("claude-code");
88 pkt.conversation.push(ConversationMessage {
89 role: "user".to_string(),
90 content: "write a parser".to_string(),
91 });
92 pkt.code_context.files.push(FileSnapshot {
93 path: "src/lib.rs".to_string(),
94 content: "fn main() {}".to_string(),
95 });
96 pkt.artifacts.push(ArtifactPayload {
97 name: "parser.rs".to_string(),
98 content: "fn parse() {}".to_string(),
99 media_type: "text/x-rust".to_string(),
100 });
101
102 let bytes = pkt.to_bytes().unwrap();
103 let restored = ContextPacket::from_bytes(&bytes).unwrap();
104 assert_eq!(pkt, restored);
105 }
106}