Skip to main content

sim_lib_openai_server/
citizen.rs

1use sim_citizen_derive::Citizen;
2use sim_kernel::{Error, Expr, Result, Symbol};
3
4use crate::{
5    content_id::content_id_for_expr,
6    objects::{GatewayEvent, GatewayRequest, GatewayResponse, GatewayRun},
7    runtime::OpenAiGatewayKey,
8};
9use sim_kernel::CapabilitySet;
10
11/// Citizen descriptor wrapping a validated `openai/GatewayRequest` expression.
12#[derive(Clone, Debug, PartialEq, Citizen)]
13#[citizen(symbol = "openai/GatewayRequest", version = 1)]
14pub struct OpenAiGatewayRequestDescriptor {
15    #[citizen(with = "gateway_request_expr")]
16    request: Expr,
17}
18
19/// Citizen descriptor wrapping a validated `openai/GatewayResponse` expression.
20#[derive(Clone, Debug, PartialEq, Citizen)]
21#[citizen(symbol = "openai/GatewayResponse", version = 1)]
22pub struct OpenAiGatewayResponseDescriptor {
23    #[citizen(with = "gateway_response_expr")]
24    response: Expr,
25}
26
27/// Citizen descriptor wrapping a validated `openai/GatewayRun` expression.
28#[derive(Clone, Debug, PartialEq, Citizen)]
29#[citizen(symbol = "openai/GatewayRun", version = 1)]
30pub struct OpenAiGatewayRunDescriptor {
31    #[citizen(with = "gateway_run_expr")]
32    run: Expr,
33}
34
35/// Citizen descriptor wrapping a validated `openai/GatewayEvent` expression.
36#[derive(Clone, Debug, PartialEq, Citizen)]
37#[citizen(symbol = "openai/GatewayEvent", version = 1)]
38pub struct OpenAiGatewayEventDescriptor {
39    #[citizen(with = "gateway_event_expr")]
40    event: Expr,
41}
42
43/// Citizen descriptor wrapping a parseable `openai/Plan` source string.
44#[derive(Clone, Debug, PartialEq, Citizen)]
45#[citizen(symbol = "openai/Plan", version = 1)]
46pub struct OpenAiPlanDescriptor {
47    #[citizen(with = "plan_source")]
48    source: String,
49}
50
51/// Citizen descriptor wrapping a validated `openai/GatewayKey` expression.
52#[derive(Clone, Debug, PartialEq, Citizen)]
53#[citizen(symbol = "openai/GatewayKey", version = 1)]
54pub struct OpenAiGatewayKeyDescriptor {
55    #[citizen(with = "gateway_key_expr")]
56    key: Expr,
57}
58
59impl OpenAiGatewayRequestDescriptor {
60    /// Validates and wraps a gateway request expression.
61    pub fn from_expr(request: Expr) -> Result<Self> {
62        gateway_request_expr::decode(&request)?;
63        Ok(Self { request })
64    }
65
66    /// Returns the wrapped gateway request expression.
67    pub fn as_expr(&self) -> &Expr {
68        &self.request
69    }
70}
71
72impl Default for OpenAiGatewayRequestDescriptor {
73    fn default() -> Self {
74        Self::from_expr(GatewayRequest::get("/v1/models").to_expr())
75            .expect("default OpenAI request descriptor should be valid")
76    }
77}
78
79impl OpenAiGatewayResponseDescriptor {
80    /// Validates and wraps a gateway response expression.
81    pub fn from_expr(response: Expr) -> Result<Self> {
82        gateway_response_expr::decode(&response)?;
83        Ok(Self { response })
84    }
85
86    /// Returns the wrapped gateway response expression.
87    pub fn as_expr(&self) -> &Expr {
88        &self.response
89    }
90}
91
92impl Default for OpenAiGatewayResponseDescriptor {
93    fn default() -> Self {
94        Self::from_expr(GatewayResponse::json(200, br#"{"ok":true}"#.to_vec()).to_expr())
95            .expect("default OpenAI response descriptor should be valid")
96    }
97}
98
99impl OpenAiGatewayRunDescriptor {
100    /// Validates and wraps a gateway run expression.
101    pub fn from_expr(run: Expr) -> Result<Self> {
102        gateway_run_expr::decode(&run)?;
103        Ok(Self { run })
104    }
105
106    /// Returns the wrapped gateway run expression.
107    pub fn as_expr(&self) -> &Expr {
108        &self.run
109    }
110}
111
112impl Default for OpenAiGatewayRunDescriptor {
113    fn default() -> Self {
114        let request_id = content_id_for_expr(&GatewayRequest::get("/v1/models").to_expr())
115            .expect("default OpenAI run content id should be valid");
116        Self::from_expr(GatewayRun::new("gwrun-citizen", request_id, 1).to_expr())
117            .expect("default OpenAI run descriptor should be valid")
118    }
119}
120
121impl OpenAiGatewayEventDescriptor {
122    /// Validates and wraps a gateway event expression.
123    pub fn from_expr(event: Expr) -> Result<Self> {
124        gateway_event_expr::decode(&event)?;
125        Ok(Self { event })
126    }
127
128    /// Returns the wrapped gateway event expression.
129    pub fn as_expr(&self) -> &Expr {
130        &self.event
131    }
132}
133
134impl Default for OpenAiGatewayEventDescriptor {
135    fn default() -> Self {
136        Self::from_expr(
137            GatewayEvent::new(
138                "gwevt-citizen",
139                "gwrun-citizen",
140                0,
141                Symbol::new("final"),
142                Expr::String("ok".to_owned()),
143                1,
144            )
145            .to_expr(),
146        )
147        .expect("default OpenAI event descriptor should be valid")
148    }
149}
150
151impl OpenAiPlanDescriptor {
152    /// Builds a descriptor from plan source text, validating that it parses.
153    pub fn new(source: impl Into<String>) -> Result<Self> {
154        let source = source.into();
155        plan_source::decode(&Expr::String(source.clone()))?;
156        Ok(Self { source })
157    }
158
159    /// Returns the wrapped plan source text.
160    pub fn source(&self) -> &str {
161        &self.source
162    }
163}
164
165impl Default for OpenAiPlanDescriptor {
166    fn default() -> Self {
167        Self::new("fixture").expect("default OpenAI plan descriptor should be valid")
168    }
169}
170
171impl OpenAiGatewayKeyDescriptor {
172    /// Validates and wraps a gateway key expression.
173    pub fn from_expr(key: Expr) -> Result<Self> {
174        gateway_key_expr::decode(&key)?;
175        Ok(Self { key })
176    }
177
178    /// Returns the wrapped gateway key expression.
179    pub fn as_expr(&self) -> &Expr {
180        &self.key
181    }
182}
183
184impl Default for OpenAiGatewayKeyDescriptor {
185    fn default() -> Self {
186        Self::from_expr(OpenAiGatewayKey::from_secret("citizen", CapabilitySet::new()).to_expr())
187            .expect("default OpenAI key descriptor should be valid")
188    }
189}
190
191/// Returns the class symbol `openai/GatewayRequest`.
192pub fn openai_gateway_request_class_symbol() -> Symbol {
193    Symbol::qualified("openai", "GatewayRequest")
194}
195
196/// Returns the class symbol `openai/GatewayResponse`.
197pub fn openai_gateway_response_class_symbol() -> Symbol {
198    Symbol::qualified("openai", "GatewayResponse")
199}
200
201/// Returns the class symbol `openai/GatewayRun`.
202pub fn openai_gateway_run_class_symbol() -> Symbol {
203    Symbol::qualified("openai", "GatewayRun")
204}
205
206/// Returns the class symbol `openai/GatewayEvent`.
207pub fn openai_gateway_event_class_symbol() -> Symbol {
208    Symbol::qualified("openai", "GatewayEvent")
209}
210
211/// Returns the class symbol `openai/Plan`.
212pub fn openai_plan_class_symbol() -> Symbol {
213    Symbol::qualified("openai", "Plan")
214}
215
216/// Returns the class symbol `openai/GatewayKey`.
217pub fn openai_gateway_key_class_symbol() -> Symbol {
218    Symbol::qualified("openai", "GatewayKey")
219}
220
221pub(crate) mod gateway_request_expr {
222    use sim_kernel::{Expr, Result};
223
224    use super::expect_object;
225
226    pub fn encode(expr: &Expr) -> Expr {
227        expr.clone()
228    }
229
230    pub fn decode(expr: &Expr) -> Result<Expr> {
231        expect_object(expr, "openai-gateway/request")?;
232        Ok(expr.clone())
233    }
234}
235
236pub(crate) mod gateway_response_expr {
237    use sim_kernel::{Expr, Result};
238
239    use super::expect_object;
240
241    pub fn encode(expr: &Expr) -> Expr {
242        expr.clone()
243    }
244
245    pub fn decode(expr: &Expr) -> Result<Expr> {
246        expect_object(expr, "openai-gateway/response")?;
247        Ok(expr.clone())
248    }
249}
250
251pub(crate) mod gateway_run_expr {
252    use sim_kernel::{Expr, Result};
253
254    use super::expect_object;
255
256    pub fn encode(expr: &Expr) -> Expr {
257        expr.clone()
258    }
259
260    pub fn decode(expr: &Expr) -> Result<Expr> {
261        expect_object(expr, "openai-gateway/run")?;
262        Ok(expr.clone())
263    }
264}
265
266pub(crate) mod gateway_event_expr {
267    use sim_kernel::{Expr, Result};
268
269    use super::expect_object;
270
271    pub fn encode(expr: &Expr) -> Expr {
272        expr.clone()
273    }
274
275    pub fn decode(expr: &Expr) -> Result<Expr> {
276        expect_object(expr, "openai-gateway/event")?;
277        Ok(expr.clone())
278    }
279}
280
281pub(crate) mod plan_source {
282    use sim_kernel::{Error, Expr, Result};
283
284    use crate::parse_plan;
285
286    pub fn encode(source: &str) -> Expr {
287        Expr::String(source.to_owned())
288    }
289
290    pub fn decode(expr: &Expr) -> Result<String> {
291        let Expr::String(source) = expr else {
292            return Err(Error::Eval(
293                "OpenAI plan descriptor source must be a string".to_owned(),
294            ));
295        };
296        parse_plan(source)?;
297        Ok(source.clone())
298    }
299}
300
301pub(crate) mod gateway_key_expr {
302    use sim_kernel::{Expr, Result};
303
304    use super::expect_object;
305
306    pub fn encode(expr: &Expr) -> Expr {
307        expr.clone()
308    }
309
310    pub fn decode(expr: &Expr) -> Result<Expr> {
311        expect_object(expr, "openai-gateway/key")?;
312        Ok(expr.clone())
313    }
314}
315
316fn expect_object(expr: &Expr, expected: &str) -> Result<()> {
317    let Expr::Map(entries) = expr else {
318        return Err(Error::Eval(format!("{expected} descriptor must be a map")));
319    };
320    let object = entries.iter().find_map(|(key, value)| {
321        field_name(key)
322            .as_deref()
323            .filter(|name| *name == "object")
324            .map(|_| value)
325    });
326    match object {
327        Some(Expr::String(value)) if value == expected => Ok(()),
328        _ => Err(Error::Eval(format!(
329            "{expected} descriptor has wrong object field"
330        ))),
331    }
332}
333
334fn field_name(expr: &Expr) -> Option<String> {
335    match expr {
336        Expr::Symbol(symbol) if symbol.namespace.is_none() => Some(symbol.name.to_string()),
337        Expr::String(value) => Some(value.clone()),
338        _ => None,
339    }
340}