Skip to main content

sim_lib_openai_server/
ops.rs

1use std::sync::Arc;
2
3use sim_kernel::{
4    Args, Callable, CapabilityName, CapabilitySet, ClassRef, Cx, Error, Expr, Object, ObjectCompat,
5    RawArgs, Result, Symbol, Value, eval_fabric_capability,
6};
7use sim_value::kind::expr_kind;
8
9use crate::{
10    capabilities::{openai_gateway_admin_capability, openai_gateway_serve_capabilities},
11    ops_admin::{call_admin_state_expr, call_run_get},
12    plan::{check_plan, eval_plan, explain_plan, parse_plan, plan_combinators_expr},
13    routes::{
14        admin::{
15            cache_stats_expr, capability_report_expr, events_expr, model_health_expr, runs_expr,
16            storage_stats_expr,
17        },
18        health::health_response,
19        models::models_response_for_runner_args,
20    },
21    runtime::{OpenAiGatewayFabric, global_openai_key_table},
22    server::{GatewayResponseValue, GatewayRoutesValue, configure_routes},
23};
24
25/// Callable runtime function exposing one gateway operation to the runtime.
26///
27/// Each instance wraps a single operation kind (serve, health, plan handling,
28/// key management, admin reporting, ...). It is registered under its
29/// [`OpenAiGatewayFunction::symbol`] and dispatched through its [`Callable`]
30/// implementation.
31#[derive(Clone)]
32pub struct OpenAiGatewayFunction {
33    kind: OpenAiGatewayFunctionKind,
34}
35
36#[derive(Clone, Copy)]
37enum OpenAiGatewayFunctionKind {
38    Serve,
39    Health,
40    Models,
41    PlanParse,
42    PlanCheck,
43    PlanRun,
44    PlanExplain,
45    PlanCombinators,
46    Fabric,
47    KeyAdd,
48    KeyList,
49    Runs,
50    RunGet,
51    Events,
52    StorageStats,
53    ModelHealth,
54    CacheStats,
55    CapabilityReport,
56}
57
58impl OpenAiGatewayFunction {
59    fn new(kind: OpenAiGatewayFunctionKind) -> Self {
60        Self { kind }
61    }
62
63    /// Returns the `openai-gateway/serve` function, which builds the configured
64    /// gateway routes.
65    pub fn serve() -> Self {
66        Self::new(OpenAiGatewayFunctionKind::Serve)
67    }
68
69    /// Returns the `openai-gateway/health` function, which produces a health
70    /// response.
71    pub fn health() -> Self {
72        Self::new(OpenAiGatewayFunctionKind::Health)
73    }
74
75    /// Returns the `openai-gateway/models` function, which lists available
76    /// models.
77    pub fn models() -> Self {
78        Self::new(OpenAiGatewayFunctionKind::Models)
79    }
80
81    /// Returns the `openai-gateway/plan-parse` function, which parses plan
82    /// source into an expression.
83    pub fn plan_parse() -> Self {
84        Self::new(OpenAiGatewayFunctionKind::PlanParse)
85    }
86
87    /// Returns the `openai-gateway/plan-check` function, which validates a plan
88    /// expression.
89    pub fn plan_check() -> Self {
90        Self::new(OpenAiGatewayFunctionKind::PlanCheck)
91    }
92
93    /// Returns the `openai-gateway/plan-run` function, which evaluates a plan
94    /// against a request.
95    pub fn plan_run() -> Self {
96        Self::new(OpenAiGatewayFunctionKind::PlanRun)
97    }
98
99    /// Returns the `openai-gateway/plan-explain` function, which renders a
100    /// human-readable explanation of a plan.
101    pub fn plan_explain() -> Self {
102        Self::new(OpenAiGatewayFunctionKind::PlanExplain)
103    }
104
105    /// Returns the `openai-gateway/plan-combinators` function, which lists the
106    /// available plan combinators.
107    pub fn plan_combinators() -> Self {
108        Self::new(OpenAiGatewayFunctionKind::PlanCombinators)
109    }
110
111    /// Returns the `openai-gateway/fabric` function, which constructs an
112    /// in-memory eval fabric.
113    pub fn fabric() -> Self {
114        Self::new(OpenAiGatewayFunctionKind::Fabric)
115    }
116
117    /// Returns the `openai-gateway/key-add` function, which registers an API
118    /// key with a capability set.
119    pub fn key_add() -> Self {
120        Self::new(OpenAiGatewayFunctionKind::KeyAdd)
121    }
122
123    /// Returns the `openai-gateway/key-list` function, which lists registered
124    /// keys.
125    pub fn key_list() -> Self {
126        Self::new(OpenAiGatewayFunctionKind::KeyList)
127    }
128
129    /// Returns the `openai-gateway/runs` function, which reports stored runs.
130    pub fn runs() -> Self {
131        Self::new(OpenAiGatewayFunctionKind::Runs)
132    }
133
134    /// Returns the `openai-gateway/run-get` function, which fetches a single
135    /// run by id.
136    pub fn run_get() -> Self {
137        Self::new(OpenAiGatewayFunctionKind::RunGet)
138    }
139
140    /// Returns the `openai-gateway/events` function, which reports stored
141    /// events.
142    pub fn events() -> Self {
143        Self::new(OpenAiGatewayFunctionKind::Events)
144    }
145
146    /// Returns the `openai-gateway/storage-stats` function, which reports
147    /// storage statistics.
148    pub fn storage_stats() -> Self {
149        Self::new(OpenAiGatewayFunctionKind::StorageStats)
150    }
151
152    /// Returns the `openai-gateway/model-health` function, which reports model
153    /// health.
154    pub fn model_health() -> Self {
155        Self::new(OpenAiGatewayFunctionKind::ModelHealth)
156    }
157
158    /// Returns the `openai-gateway/cache-stats` function, which reports plan
159    /// cache statistics.
160    pub fn cache_stats() -> Self {
161        Self::new(OpenAiGatewayFunctionKind::CacheStats)
162    }
163
164    /// Returns the `openai-gateway/capability-report` function, which reports
165    /// the gateway capability set.
166    pub fn capability_report() -> Self {
167        Self::new(OpenAiGatewayFunctionKind::CapabilityReport)
168    }
169
170    /// Returns the qualified symbol this function is registered under.
171    pub fn symbol(&self) -> Symbol {
172        self.kind.symbol()
173    }
174}
175
176impl Object for OpenAiGatewayFunction {
177    fn display(&self, _cx: &mut Cx) -> Result<String> {
178        Ok(format!("#<function {}>", self.kind.symbol()))
179    }
180
181    fn as_any(&self) -> &dyn std::any::Any {
182        self
183    }
184}
185
186impl ObjectCompat for OpenAiGatewayFunction {
187    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
188        cx.resolve_class(&Symbol::qualified("core", "Function"))
189    }
190
191    fn as_callable(&self) -> Option<&dyn Callable> {
192        Some(self)
193    }
194}
195
196impl Callable for OpenAiGatewayFunction {
197    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
198        self.kind.call(cx, args.into_vec())
199    }
200
201    fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
202        let values = args
203            .into_exprs()
204            .into_iter()
205            .map(|expr| cx.eval_expr(expr))
206            .collect::<Result<Vec<_>>>()?;
207        self.kind.call(cx, values)
208    }
209}
210
211impl OpenAiGatewayFunctionKind {
212    fn symbol(self) -> Symbol {
213        match self {
214            Self::Serve => serve_symbol(),
215            Self::Health => health_symbol(),
216            Self::Models => models_symbol(),
217            Self::PlanParse => plan_parse_symbol(),
218            Self::PlanCheck => plan_check_symbol(),
219            Self::PlanRun => plan_run_symbol(),
220            Self::PlanExplain => plan_explain_symbol(),
221            Self::PlanCombinators => plan_combinators_symbol(),
222            Self::Fabric => fabric_symbol(),
223            Self::KeyAdd => key_add_symbol(),
224            Self::KeyList => key_list_symbol(),
225            Self::Runs => runs_symbol(),
226            Self::RunGet => run_get_symbol(),
227            Self::Events => events_symbol(),
228            Self::StorageStats => storage_stats_symbol(),
229            Self::ModelHealth => model_health_symbol(),
230            Self::CacheStats => cache_stats_symbol(),
231            Self::CapabilityReport => capability_report_symbol(),
232        }
233    }
234
235    fn call(self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
236        match self {
237            Self::Serve | Self::Health | Self::PlanCombinators | Self::Fabric | Self::KeyList
238                if !args.is_empty() =>
239            {
240                Err(Error::Eval(format!(
241                    "{} expects no arguments",
242                    self.symbol()
243                )))
244            }
245            Self::Serve => call_serve(cx),
246            Self::Health => call_health(cx),
247            Self::Models => call_models(cx, args),
248            Self::PlanParse => call_plan_parse(cx, args),
249            Self::PlanCheck => call_plan_check(cx, args),
250            Self::PlanRun => call_plan_run(cx, args),
251            Self::PlanExplain => call_plan_explain(cx, args),
252            Self::PlanCombinators => call_plan_combinators(cx),
253            Self::Fabric => call_fabric(cx),
254            Self::KeyAdd => call_key_add(cx, args),
255            Self::KeyList => call_key_list(cx),
256            Self::Runs => call_admin_state_expr(cx, self.symbol(), args, runs_expr),
257            Self::RunGet => call_run_get(cx, self.symbol(), args),
258            Self::Events => call_admin_state_expr(cx, self.symbol(), args, events_expr),
259            Self::StorageStats => {
260                call_admin_state_expr(cx, self.symbol(), args, storage_stats_expr)
261            }
262            Self::ModelHealth => call_admin_state_expr(cx, self.symbol(), args, model_health_expr),
263            Self::CacheStats => call_admin_state_expr(cx, self.symbol(), args, cache_stats_expr),
264            Self::CapabilityReport => {
265                call_admin_state_expr(cx, self.symbol(), args, capability_report_expr)
266            }
267        }
268    }
269}
270
271/// Returns the `openai-gateway/serve` operation symbol.
272pub fn serve_symbol() -> Symbol {
273    Symbol::qualified("openai-gateway", "serve")
274}
275
276/// Returns the `openai-gateway/health` operation symbol.
277pub fn health_symbol() -> Symbol {
278    Symbol::qualified("openai-gateway", "health")
279}
280
281/// Returns the `openai-gateway/models` operation symbol.
282pub fn models_symbol() -> Symbol {
283    Symbol::qualified("openai-gateway", "models")
284}
285
286/// Returns the `openai-gateway/plan-parse` operation symbol.
287pub fn plan_parse_symbol() -> Symbol {
288    Symbol::qualified("openai-gateway", "plan-parse")
289}
290
291/// Returns the `openai-gateway/plan-check` operation symbol.
292pub fn plan_check_symbol() -> Symbol {
293    Symbol::qualified("openai-gateway", "plan-check")
294}
295
296/// Returns the `openai-gateway/plan-run` operation symbol.
297pub fn plan_run_symbol() -> Symbol {
298    Symbol::qualified("openai-gateway", "plan-run")
299}
300
301/// Returns the `openai-gateway/plan-explain` operation symbol.
302pub fn plan_explain_symbol() -> Symbol {
303    Symbol::qualified("openai-gateway", "plan-explain")
304}
305
306/// Returns the `openai-gateway/plan-combinators` operation symbol.
307pub fn plan_combinators_symbol() -> Symbol {
308    Symbol::qualified("openai-gateway", "plan-combinators")
309}
310
311/// Returns the `openai-gateway/fabric` operation symbol.
312pub fn fabric_symbol() -> Symbol {
313    Symbol::qualified("openai-gateway", "fabric")
314}
315
316/// Returns the `openai-gateway/key-add` operation symbol.
317pub fn key_add_symbol() -> Symbol {
318    Symbol::qualified("openai-gateway", "key-add")
319}
320
321/// Returns the `openai-gateway/key-list` operation symbol.
322pub fn key_list_symbol() -> Symbol {
323    Symbol::qualified("openai-gateway", "key-list")
324}
325
326/// Returns the `openai-gateway/runs` operation symbol.
327pub fn runs_symbol() -> Symbol {
328    Symbol::qualified("openai-gateway", "runs")
329}
330
331/// Returns the `openai-gateway/run-get` operation symbol.
332pub fn run_get_symbol() -> Symbol {
333    Symbol::qualified("openai-gateway", "run-get")
334}
335
336/// Returns the `openai-gateway/events` operation symbol.
337pub fn events_symbol() -> Symbol {
338    Symbol::qualified("openai-gateway", "events")
339}
340
341/// Returns the `openai-gateway/storage-stats` operation symbol.
342pub fn storage_stats_symbol() -> Symbol {
343    Symbol::qualified("openai-gateway", "storage-stats")
344}
345
346/// Returns the `openai-gateway/model-health` operation symbol.
347pub fn model_health_symbol() -> Symbol {
348    Symbol::qualified("openai-gateway", "model-health")
349}
350
351/// Returns the `openai-gateway/cache-stats` operation symbol.
352pub fn cache_stats_symbol() -> Symbol {
353    Symbol::qualified("openai-gateway", "cache-stats")
354}
355
356/// Returns the `openai-gateway/capability-report` operation symbol.
357pub fn capability_report_symbol() -> Symbol {
358    Symbol::qualified("openai-gateway", "capability-report")
359}
360
361fn call_serve(cx: &mut Cx) -> Result<Value> {
362    cx.require_all(&openai_gateway_serve_capabilities())?;
363    cx.factory()
364        .opaque(Arc::new(GatewayRoutesValue::new(configure_routes())))
365}
366
367fn call_health(cx: &mut Cx) -> Result<Value> {
368    cx.factory()
369        .opaque(Arc::new(GatewayResponseValue::new(health_response())))
370}
371
372fn call_models(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
373    let response = models_response_for_runner_args(cx, args)?;
374    cx.factory()
375        .opaque(Arc::new(GatewayResponseValue::new(response)))
376}
377
378fn call_plan_parse(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
379    let input = string_arg(cx, plan_parse_symbol(), args)?;
380    cx.factory().expr(parse_plan(&input)?)
381}
382
383fn call_plan_check(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
384    let plan = expr_arg(cx, plan_check_symbol(), args)?;
385    check_plan(&plan)?;
386    cx.factory().bool(true)
387}
388
389fn call_plan_run(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
390    let mut args = expect_arg_count(plan_run_symbol(), args, 2)?.into_iter();
391    let plan = value_expr(cx, args.next().expect("plan-run arg count checked"))?;
392    let request = value_expr(cx, args.next().expect("plan-run arg count checked"))?;
393    let response = eval_plan(cx, &plan, &request)?;
394    cx.factory().expr(response)
395}
396
397fn call_plan_explain(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
398    let plan = expr_arg(cx, plan_explain_symbol(), args)?;
399    cx.factory().string(explain_plan(&plan)?)
400}
401
402fn call_plan_combinators(cx: &mut Cx) -> Result<Value> {
403    cx.factory().expr(plan_combinators_expr())
404}
405
406fn call_fabric(cx: &mut Cx) -> Result<Value> {
407    cx.require(&eval_fabric_capability())?;
408    cx.factory().opaque(Arc::new(OpenAiGatewayFabric::memory()))
409}
410
411fn call_key_add(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
412    cx.require(&openai_gateway_admin_capability())?;
413    if args.is_empty() {
414        return Err(Error::Eval(format!(
415            "{} expects at least 1 argument",
416            key_add_symbol()
417        )));
418    }
419    let mut args = args.into_iter();
420    let secret = value_string(
421        cx,
422        args.next().expect("key-add arg count checked as non-empty"),
423    )?;
424    let mut capabilities = CapabilitySet::new();
425    for arg in args {
426        capabilities.insert(value_capability(cx, arg)?);
427    }
428    let key = global_openai_key_table().add_secret(&secret, capabilities)?;
429    cx.factory().opaque(Arc::new(key))
430}
431
432fn call_key_list(cx: &mut Cx) -> Result<Value> {
433    cx.require(&openai_gateway_admin_capability())?;
434    let keys = global_openai_key_table().list_keys()?;
435    cx.factory().list(
436        keys.into_iter()
437            .map(|key| cx.factory().opaque(Arc::new(key)))
438            .collect::<Result<Vec<_>>>()?,
439    )
440}
441
442fn string_arg(cx: &mut Cx, symbol: Symbol, args: Vec<Value>) -> Result<String> {
443    match expr_arg(cx, symbol.clone(), args)? {
444        Expr::String(value) => Ok(value),
445        other => Err(Error::TypeMismatch {
446            expected: "string argument",
447            found: expr_kind(&other),
448        }),
449    }
450}
451
452fn value_string(cx: &mut Cx, value: Value) -> Result<String> {
453    match value_expr(cx, value)? {
454        Expr::String(value) => Ok(value),
455        other => Err(Error::TypeMismatch {
456            expected: "string argument",
457            found: expr_kind(&other),
458        }),
459    }
460}
461
462fn value_capability(cx: &mut Cx, value: Value) -> Result<CapabilityName> {
463    match value_expr(cx, value)? {
464        Expr::String(value) => Ok(CapabilityName::new(value)),
465        Expr::Symbol(symbol) => Ok(CapabilityName::new(symbol.to_string())),
466        other => Err(Error::TypeMismatch {
467            expected: "capability string or symbol",
468            found: expr_kind(&other),
469        }),
470    }
471}
472
473fn expr_arg(cx: &mut Cx, symbol: Symbol, args: Vec<Value>) -> Result<Expr> {
474    let mut args = expect_arg_count(symbol, args, 1)?.into_iter();
475    value_expr(cx, args.next().expect("arg count checked"))
476}
477
478fn expect_arg_count(symbol: Symbol, args: Vec<Value>, expected: usize) -> Result<Vec<Value>> {
479    if args.len() == expected {
480        Ok(args)
481    } else {
482        Err(Error::Eval(format!(
483            "{symbol} expects {expected} argument(s), found {}",
484            args.len()
485        )))
486    }
487}
488
489fn value_expr(cx: &mut Cx, value: Value) -> Result<Expr> {
490    value.object().as_expr(cx)
491}