sim_lib_openai_server/runtime/
cache.rs1use std::collections::BTreeMap;
2
3use sim_kernel::{ContentId, Cx, Error, Expr, Result};
4
5use crate::{
6 capabilities::ai_runner_cache_capability,
7 content_id::{content_id_for_expr, request_expr_content_id},
8 objects::content_id_expr,
9};
10
11#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
17pub struct PlanCacheKey {
18 request_content_id: ContentId,
19 plan_content_id: ContentId,
20}
21
22impl PlanCacheKey {
23 pub fn new(request_content_id: ContentId, plan_content_id: ContentId) -> Self {
25 Self {
26 request_content_id,
27 plan_content_id,
28 }
29 }
30
31 pub fn for_request_plan(request: &Expr, plan: &Expr) -> Result<Self> {
33 Ok(Self::new(
34 request_expr_content_id(request)?,
35 content_id_for_expr(plan)?,
36 ))
37 }
38
39 pub fn request_content_id(&self) -> &ContentId {
41 &self.request_content_id
42 }
43
44 pub fn plan_content_id(&self) -> &ContentId {
46 &self.plan_content_id
47 }
48
49 pub fn to_expr(&self) -> Expr {
51 Expr::Map(vec![
52 field(
53 "request-content-id",
54 content_id_expr(&self.request_content_id),
55 ),
56 field("plan-content-id", content_id_expr(&self.plan_content_id)),
57 ])
58 }
59}
60
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
63pub enum PlanCacheMode {
64 Disabled,
66 #[default]
68 ReadThrough,
69 ReadOnly,
71 WriteOnly,
73 Refresh,
75}
76
77impl PlanCacheMode {
78 pub fn from_expr(expr: &Expr) -> Result<Self> {
80 match expr {
81 Expr::String(name) => Self::from_name(name),
82 Expr::Symbol(symbol) if symbol.namespace.is_none() => Self::from_name(&symbol.name),
83 Expr::List(items) => match items.as_slice() {
84 [Expr::Symbol(head), Expr::String(name)]
85 if head.namespace.is_none() && head.name.as_ref() == "plan/atom" =>
86 {
87 Self::from_name(name)
88 }
89 _ => Err(Error::Eval(
90 "plan/cache mode must be a symbol, string, or atom".to_owned(),
91 )),
92 },
93 _ => Err(Error::Eval(
94 "plan/cache mode must be a symbol, string, or atom".to_owned(),
95 )),
96 }
97 }
98
99 pub fn from_name(name: &str) -> Result<Self> {
101 match name {
102 "off" | "none" | "disabled" => Ok(Self::Disabled),
103 "read-through" | "read-write" | "default" => Ok(Self::ReadThrough),
104 "read" | "read-only" => Ok(Self::ReadOnly),
105 "write" | "write-only" => Ok(Self::WriteOnly),
106 "refresh" => Ok(Self::Refresh),
107 other => Err(Error::Eval(format!("unsupported plan/cache mode {other}"))),
108 }
109 }
110
111 pub fn reads(self) -> bool {
113 matches!(self, Self::ReadThrough | Self::ReadOnly)
114 }
115
116 pub fn writes(self) -> bool {
118 matches!(self, Self::ReadThrough | Self::WriteOnly | Self::Refresh)
119 }
120}
121
122#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124pub enum PlanCacheWriteTarget {
125 Memory,
127 Persistent,
129}
130
131#[derive(Clone, Debug, Default)]
133pub struct OpenAiPlanCache {
134 entries: BTreeMap<PlanCacheKey, Expr>,
135}
136
137impl OpenAiPlanCache {
138 pub fn new() -> Self {
140 Self::default()
141 }
142
143 pub fn get(&self, key: &PlanCacheKey) -> Option<&Expr> {
145 self.entries.get(key)
146 }
147
148 pub fn put(
154 &mut self,
155 cx: &mut Cx,
156 target: PlanCacheWriteTarget,
157 key: PlanCacheKey,
158 response: Expr,
159 ) -> Result<()> {
160 if target == PlanCacheWriteTarget::Persistent {
161 cx.require(&ai_runner_cache_capability())?;
162 }
163 self.entries.insert(key, response);
164 Ok(())
165 }
166
167 pub fn len(&self) -> usize {
169 self.entries.len()
170 }
171
172 pub fn is_empty(&self) -> bool {
174 self.entries.is_empty()
175 }
176}
177
178use sim_value::build::entry as field;
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn cache_modes_report_read_and_write_behavior() {
186 let cases = [
187 ("disabled", false, false),
188 ("read-through", true, true),
189 ("read-only", true, false),
190 ("write-only", false, true),
191 ("refresh", false, true),
192 ];
193
194 for (name, reads, writes) in cases {
195 let mode = PlanCacheMode::from_name(name).unwrap();
196 assert_eq!(mode.reads(), reads, "{name} read behavior");
197 assert_eq!(mode.writes(), writes, "{name} write behavior");
198 }
199 }
200}