Skip to main content

sim_lib_skill/
transport.rs

1use std::sync::Arc;
2
3use sim_citizen_derive::non_citizen;
4use sim_kernel::{Cx, Expr, Object, ObjectCompat, Result, Symbol, Value};
5
6use crate::SkillCard;
7
8/// Sink that receives streaming events emitted during a skill call.
9pub trait SkillEventSink {
10    /// Emits one `event` produced while a skill call is in progress.
11    fn emit(&mut self, cx: &mut Cx, event: Value) -> Result<()>;
12}
13
14/// Backend that discovers and runs skills.
15///
16/// A transport is the concrete behavior behind a [`SkillCard`]: it knows how
17/// to enumerate available skills and how to dispatch a call for one.
18/// Implementations include the in-process [`FixtureTransport`] and the
19/// feature-gated MCP, HTTP, process, and OpenAI-server transports.
20///
21/// [`FixtureTransport`]: crate::FixtureTransport
22pub trait SkillTransport: Send + Sync {
23    /// Returns the transport's stable identifier.
24    fn id(&self) -> &str;
25    /// Returns the transport's kind (for example `fixture`, `mcp`, `http`).
26    fn kind(&self) -> &str;
27    /// Discovers the skills this transport can run, as cards.
28    fn discover(&self, cx: &mut Cx) -> Result<Vec<SkillCard>>;
29    /// Runs the skill described by `card` with the given `args`.
30    ///
31    /// The optional `events` sink receives any streaming events emitted while
32    /// the call is in progress.
33    fn call(
34        &self,
35        cx: &mut Cx,
36        card: &SkillCard,
37        args: Value,
38        events: Option<&mut dyn SkillEventSink>,
39    ) -> Result<Value>;
40    /// Returns a value describing the transport's health.
41    fn health(&self, cx: &mut Cx) -> Result<Value>;
42}
43
44/// Runtime [`Object`] wrapper around a shared [`SkillTransport`].
45///
46/// This is the object passed to `skill/install` to register a transport. It is
47/// a live handle; its transport metadata is also carried by the `skill/Card`
48/// descriptor.
49#[derive(Clone)]
50#[non_citizen(
51    reason = "live skill transport handle; transport metadata is carried by skill/Card descriptor",
52    kind = "handle"
53)]
54pub struct SkillTransportValue {
55    transport: Arc<dyn SkillTransport>,
56}
57
58impl SkillTransportValue {
59    /// Wraps `transport` in a runtime value.
60    pub fn new(transport: Arc<dyn SkillTransport>) -> Self {
61        Self { transport }
62    }
63
64    /// Returns a shared handle to the wrapped transport.
65    pub fn transport(&self) -> Arc<dyn SkillTransport> {
66        self.transport.clone()
67    }
68}
69
70impl Object for SkillTransportValue {
71    fn display(&self, _cx: &mut Cx) -> Result<String> {
72        Ok(format!(
73            "#<skill-transport {}:{}>",
74            self.transport.kind(),
75            self.transport.id()
76        ))
77    }
78
79    fn as_any(&self) -> &dyn std::any::Any {
80        self
81    }
82}
83
84impl ObjectCompat for SkillTransportValue {
85    fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
86        self.as_table(cx)?.object().as_expr(cx)
87    }
88
89    fn as_table(&self, cx: &mut Cx) -> Result<Value> {
90        cx.factory().table(vec![
91            (
92                Symbol::new("kind"),
93                cx.factory().symbol(Symbol::new("skill/transport"))?,
94            ),
95            (
96                Symbol::new("id"),
97                cx.factory().string(self.transport.id().to_owned())?,
98            ),
99            (
100                Symbol::new("transport-kind"),
101                cx.factory().string(self.transport.kind().to_owned())?,
102            ),
103        ])
104    }
105}
106
107/// Wraps `transport` in an opaque [`SkillTransportValue`] runtime value.
108pub fn skill_transport_value(cx: &mut Cx, transport: Arc<dyn SkillTransport>) -> Result<Value> {
109    cx.factory()
110        .opaque(Arc::new(SkillTransportValue::new(transport)))
111}