git_internal/internal/object/
context_frame.rs1use std::fmt;
32
33use serde::{Deserialize, Serialize};
34use uuid::Uuid;
35
36use crate::{
37 errors::GitError,
38 hash::ObjectHash,
39 internal::object::{
40 ObjectTrait,
41 types::{ActorRef, Header, ObjectType},
42 },
43};
44
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46#[serde(rename_all = "snake_case")]
47pub enum FrameKind {
48 IntentAnalysis,
49 StepSummary,
50 CodeChange,
51 SystemState,
52 ErrorRecovery,
53 Checkpoint,
54 ToolCall,
55 Other(String),
56}
57
58impl FrameKind {
59 pub fn as_str(&self) -> &str {
62 match self {
63 FrameKind::IntentAnalysis => "intent_analysis",
64 FrameKind::StepSummary => "step_summary",
65 FrameKind::CodeChange => "code_change",
66 FrameKind::SystemState => "system_state",
67 FrameKind::ErrorRecovery => "error_recovery",
68 FrameKind::Checkpoint => "checkpoint",
69 FrameKind::ToolCall => "tool_call",
70 FrameKind::Other(value) => value.as_str(),
71 }
72 }
73}
74
75impl fmt::Display for FrameKind {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 write!(f, "{}", self.as_str())
78 }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(deny_unknown_fields)]
87pub struct ContextFrame {
88 #[serde(flatten)]
91 header: Header,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
94 intent_id: Option<Uuid>,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
97 run_id: Option<Uuid>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
100 plan_id: Option<Uuid>,
101 #[serde(default, skip_serializing_if = "Option::is_none")]
103 step_id: Option<Uuid>,
104 kind: FrameKind,
106 summary: String,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
110 data: Option<serde_json::Value>,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
113 token_estimate: Option<u64>,
114}
115
116impl ContextFrame {
117 pub fn new(
120 created_by: ActorRef,
121 kind: FrameKind,
122 summary: impl Into<String>,
123 ) -> Result<Self, String> {
124 Ok(Self {
125 header: Header::new(ObjectType::ContextFrame, created_by)?,
126 intent_id: None,
127 run_id: None,
128 plan_id: None,
129 step_id: None,
130 kind,
131 summary: summary.into(),
132 data: None,
133 token_estimate: None,
134 })
135 }
136
137 pub fn header(&self) -> &Header {
139 &self.header
140 }
141
142 pub fn intent_id(&self) -> Option<Uuid> {
144 self.intent_id
145 }
146
147 pub fn run_id(&self) -> Option<Uuid> {
149 self.run_id
150 }
151
152 pub fn plan_id(&self) -> Option<Uuid> {
154 self.plan_id
155 }
156
157 pub fn step_id(&self) -> Option<Uuid> {
159 self.step_id
160 }
161
162 pub fn kind(&self) -> &FrameKind {
164 &self.kind
165 }
166
167 pub fn summary(&self) -> &str {
169 &self.summary
170 }
171
172 pub fn data(&self) -> Option<&serde_json::Value> {
174 self.data.as_ref()
175 }
176
177 pub fn token_estimate(&self) -> Option<u64> {
179 self.token_estimate
180 }
181
182 pub fn set_intent_id(&mut self, intent_id: Option<Uuid>) {
184 self.intent_id = intent_id;
185 }
186
187 pub fn set_run_id(&mut self, run_id: Option<Uuid>) {
189 self.run_id = run_id;
190 }
191
192 pub fn set_plan_id(&mut self, plan_id: Option<Uuid>) {
194 self.plan_id = plan_id;
195 }
196
197 pub fn set_step_id(&mut self, step_id: Option<Uuid>) {
199 self.step_id = step_id;
200 }
201
202 pub fn set_data(&mut self, data: Option<serde_json::Value>) {
204 self.data = data;
205 }
206
207 pub fn set_token_estimate(&mut self, token_estimate: Option<u64>) {
209 self.token_estimate = token_estimate;
210 }
211}
212
213impl fmt::Display for ContextFrame {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 write!(f, "ContextFrame: {}", self.header.object_id())
216 }
217}
218
219impl ObjectTrait for ContextFrame {
220 fn from_bytes(data: &[u8], _hash: ObjectHash) -> Result<Self, GitError>
221 where
222 Self: Sized,
223 {
224 serde_json::from_slice(data).map_err(|e| GitError::InvalidContextFrameObject(e.to_string()))
225 }
226
227 fn get_type(&self) -> ObjectType {
228 ObjectType::ContextFrame
229 }
230
231 fn get_size(&self) -> usize {
232 match serde_json::to_vec(self) {
233 Ok(v) => v.len(),
234 Err(e) => {
235 tracing::warn!("failed to compute ContextFrame size: {}", e);
236 0
237 }
238 }
239 }
240
241 fn to_data(&self) -> Result<Vec<u8>, GitError> {
242 serde_json::to_vec(self).map_err(|e| GitError::InvalidContextFrameObject(e.to_string()))
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
256 fn test_context_frame_fields() {
257 let actor = ActorRef::agent("planner").expect("actor");
258 let mut frame =
259 ContextFrame::new(actor, FrameKind::StepSummary, "Updated API").expect("frame");
260 let intent_id = Uuid::from_u128(0x0f);
261 let run_id = Uuid::from_u128(0x10);
262 let plan_id = Uuid::from_u128(0x11);
263 let step_id = Uuid::from_u128(0x12);
264
265 frame.set_intent_id(Some(intent_id));
266 frame.set_run_id(Some(run_id));
267 frame.set_plan_id(Some(plan_id));
268 frame.set_step_id(Some(step_id));
269 frame.set_data(Some(serde_json::json!({"files": ["src/lib.rs"]})));
270 frame.set_token_estimate(Some(128));
271
272 assert_eq!(frame.intent_id(), Some(intent_id));
273 assert_eq!(frame.run_id(), Some(run_id));
274 assert_eq!(frame.plan_id(), Some(plan_id));
275 assert_eq!(frame.step_id(), Some(step_id));
276 assert_eq!(frame.kind(), &FrameKind::StepSummary);
277 assert_eq!(frame.token_estimate(), Some(128));
278 }
279}