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#[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 pub fn serve() -> Self {
66 Self::new(OpenAiGatewayFunctionKind::Serve)
67 }
68
69 pub fn health() -> Self {
72 Self::new(OpenAiGatewayFunctionKind::Health)
73 }
74
75 pub fn models() -> Self {
78 Self::new(OpenAiGatewayFunctionKind::Models)
79 }
80
81 pub fn plan_parse() -> Self {
84 Self::new(OpenAiGatewayFunctionKind::PlanParse)
85 }
86
87 pub fn plan_check() -> Self {
90 Self::new(OpenAiGatewayFunctionKind::PlanCheck)
91 }
92
93 pub fn plan_run() -> Self {
96 Self::new(OpenAiGatewayFunctionKind::PlanRun)
97 }
98
99 pub fn plan_explain() -> Self {
102 Self::new(OpenAiGatewayFunctionKind::PlanExplain)
103 }
104
105 pub fn plan_combinators() -> Self {
108 Self::new(OpenAiGatewayFunctionKind::PlanCombinators)
109 }
110
111 pub fn fabric() -> Self {
114 Self::new(OpenAiGatewayFunctionKind::Fabric)
115 }
116
117 pub fn key_add() -> Self {
120 Self::new(OpenAiGatewayFunctionKind::KeyAdd)
121 }
122
123 pub fn key_list() -> Self {
126 Self::new(OpenAiGatewayFunctionKind::KeyList)
127 }
128
129 pub fn runs() -> Self {
131 Self::new(OpenAiGatewayFunctionKind::Runs)
132 }
133
134 pub fn run_get() -> Self {
137 Self::new(OpenAiGatewayFunctionKind::RunGet)
138 }
139
140 pub fn events() -> Self {
143 Self::new(OpenAiGatewayFunctionKind::Events)
144 }
145
146 pub fn storage_stats() -> Self {
149 Self::new(OpenAiGatewayFunctionKind::StorageStats)
150 }
151
152 pub fn model_health() -> Self {
155 Self::new(OpenAiGatewayFunctionKind::ModelHealth)
156 }
157
158 pub fn cache_stats() -> Self {
161 Self::new(OpenAiGatewayFunctionKind::CacheStats)
162 }
163
164 pub fn capability_report() -> Self {
167 Self::new(OpenAiGatewayFunctionKind::CapabilityReport)
168 }
169
170 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
271pub fn serve_symbol() -> Symbol {
273 Symbol::qualified("openai-gateway", "serve")
274}
275
276pub fn health_symbol() -> Symbol {
278 Symbol::qualified("openai-gateway", "health")
279}
280
281pub fn models_symbol() -> Symbol {
283 Symbol::qualified("openai-gateway", "models")
284}
285
286pub fn plan_parse_symbol() -> Symbol {
288 Symbol::qualified("openai-gateway", "plan-parse")
289}
290
291pub fn plan_check_symbol() -> Symbol {
293 Symbol::qualified("openai-gateway", "plan-check")
294}
295
296pub fn plan_run_symbol() -> Symbol {
298 Symbol::qualified("openai-gateway", "plan-run")
299}
300
301pub fn plan_explain_symbol() -> Symbol {
303 Symbol::qualified("openai-gateway", "plan-explain")
304}
305
306pub fn plan_combinators_symbol() -> Symbol {
308 Symbol::qualified("openai-gateway", "plan-combinators")
309}
310
311pub fn fabric_symbol() -> Symbol {
313 Symbol::qualified("openai-gateway", "fabric")
314}
315
316pub fn key_add_symbol() -> Symbol {
318 Symbol::qualified("openai-gateway", "key-add")
319}
320
321pub fn key_list_symbol() -> Symbol {
323 Symbol::qualified("openai-gateway", "key-list")
324}
325
326pub fn runs_symbol() -> Symbol {
328 Symbol::qualified("openai-gateway", "runs")
329}
330
331pub fn run_get_symbol() -> Symbol {
333 Symbol::qualified("openai-gateway", "run-get")
334}
335
336pub fn events_symbol() -> Symbol {
338 Symbol::qualified("openai-gateway", "events")
339}
340
341pub fn storage_stats_symbol() -> Symbol {
343 Symbol::qualified("openai-gateway", "storage-stats")
344}
345
346pub fn model_health_symbol() -> Symbol {
348 Symbol::qualified("openai-gateway", "model-health")
349}
350
351pub fn cache_stats_symbol() -> Symbol {
353 Symbol::qualified("openai-gateway", "cache-stats")
354}
355
356pub 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}