codex_convert_proxy/proxy/
context_store.rs1use std::collections::{HashMap, VecDeque};
4use std::sync::RwLock;
5
6use crate::types::chat_api::ChatMessage;
7
8#[derive(Debug, Clone)]
10pub struct ConversationSnapshot {
11 pub instructions: Option<String>,
12 pub messages: Vec<ChatMessage>,
13}
14
15const MAX_CONVERSATION_ENTRIES: usize = 1024;
16
17#[derive(Debug, Default)]
18struct StoreInner {
19 map: HashMap<String, ConversationSnapshot>,
20 lru_order: VecDeque<String>,
21}
22
23#[derive(Debug, Default)]
25pub struct ConversationStore {
26 inner: RwLock<StoreInner>,
27}
28
29impl ConversationStore {
30 pub fn new() -> Self {
31 Self::default()
32 }
33
34 pub fn get(&self, response_id: &str) -> Option<ConversationSnapshot> {
35 let mut guard = self.inner.write().ok()?;
36 let snapshot = guard.map.get(response_id).cloned()?;
37 if let Some(pos) = guard.lru_order.iter().position(|k| k == response_id) {
38 guard.lru_order.remove(pos);
39 }
40 guard.lru_order.push_back(response_id.to_string());
41 Some(snapshot)
42 }
43
44 pub fn insert(&self, response_id: String, snapshot: ConversationSnapshot) {
45 if let Ok(mut guard) = self.inner.write() {
46 if let Some(pos) = guard.lru_order.iter().position(|k| k == &response_id) {
47 guard.lru_order.remove(pos);
48 }
49 guard.lru_order.push_back(response_id.clone());
50 guard.map.insert(response_id, snapshot);
51
52 while guard.map.len() > MAX_CONVERSATION_ENTRIES {
53 if let Some(oldest_key) = guard.lru_order.pop_front() {
54 guard.map.remove(&oldest_key);
55 } else {
56 break;
57 }
58 }
59 }
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use crate::types::chat_api::{ChatMessage, Content, MessageRole};
67
68 fn snapshot(text: &str) -> ConversationSnapshot {
69 ConversationSnapshot {
70 instructions: Some("test".to_string()),
71 messages: vec![ChatMessage {
72 role: MessageRole::User,
73 content: Content::String(text.to_string()),
74 name: None,
75 annotations: None,
76 tool_calls: None,
77 function_call: None,
78 tool_call_id: None,
79 refusal: None,
80 }],
81 }
82 }
83
84 #[test]
85 fn test_lru_eviction_keeps_recent_entries() {
86 let store = ConversationStore::new();
87 for i in 0..=MAX_CONVERSATION_ENTRIES {
88 store.insert(format!("resp_{i}"), snapshot("x"));
89 }
90 assert!(store.get("resp_0").is_none());
91 assert!(store.get(&format!("resp_{}", MAX_CONVERSATION_ENTRIES)).is_some());
92 }
93
94 #[test]
95 fn test_get_refreshes_lru_order() {
96 let store = ConversationStore::new();
97 for i in 0..MAX_CONVERSATION_ENTRIES {
98 store.insert(format!("resp_{i}"), snapshot("x"));
99 }
100 assert!(store.get("resp_0").is_some());
102 store.insert("resp_new".to_string(), snapshot("y"));
104 assert!(store.get("resp_0").is_some());
105 assert!(store.get("resp_1").is_none());
106 }
107}