1use std::sync::Arc;
2
3use sim_kernel::{
4 Args, Callable, CapabilityName, Cx, Error, Export, Object, ObjectCompat, Result, Symbol, Value,
5};
6use sim_shape::{AnyShape, ListShape, Shape, shape_value};
7
8use crate::registry::{card_from_value, transport_from_value};
9
10#[derive(Clone, Copy)]
12pub enum SkillFunctionKind {
13 Install,
15 Bind,
17 List,
19 Card,
21 Call,
23 #[cfg(any(feature = "cache", feature = "cassette"))]
25 Audit,
26 #[cfg(feature = "agent")]
28 AsTool,
29 #[cfg(feature = "mcp")]
31 McpTools,
32 #[cfg(feature = "mcp")]
34 McpCall,
35 #[cfg(feature = "openai")]
37 OpenAiTool,
38 #[cfg(feature = "openai")]
40 OpenAiTools,
41 #[cfg(feature = "runner")]
43 AsRunner,
44 #[cfg(feature = "serve")]
46 ServeMcp,
47}
48
49impl SkillFunctionKind {
50 pub fn symbol(self) -> Symbol {
52 match self {
53 SkillFunctionKind::Install => skill_install_symbol(),
54 SkillFunctionKind::Bind => skill_bind_symbol(),
55 SkillFunctionKind::List => skill_list_symbol(),
56 SkillFunctionKind::Card => skill_card_symbol(),
57 SkillFunctionKind::Call => skill_call_symbol(),
58 #[cfg(any(feature = "cache", feature = "cassette"))]
59 SkillFunctionKind::Audit => skill_audit_symbol(),
60 #[cfg(feature = "agent")]
61 SkillFunctionKind::AsTool => crate::agent::skill_as_tool_symbol(),
62 #[cfg(feature = "mcp")]
63 SkillFunctionKind::McpTools => crate::mcp::skill_mcp_tools_symbol(),
64 #[cfg(feature = "mcp")]
65 SkillFunctionKind::McpCall => crate::mcp::skill_mcp_call_symbol(),
66 #[cfg(feature = "openai")]
67 SkillFunctionKind::OpenAiTool => crate::openai::skill_openai_tool_symbol(),
68 #[cfg(feature = "openai")]
69 SkillFunctionKind::OpenAiTools => crate::openai::skill_openai_tools_symbol(),
70 #[cfg(feature = "runner")]
71 SkillFunctionKind::AsRunner => crate::runner::skill_as_runner_symbol(),
72 #[cfg(feature = "serve")]
73 SkillFunctionKind::ServeMcp => crate::serve::skill_serve_mcp_symbol(),
74 }
75 }
76}
77
78#[derive(Clone)]
80pub struct SkillFunction {
81 kind: SkillFunctionKind,
82}
83
84impl SkillFunction {
85 pub fn new(kind: SkillFunctionKind) -> Self {
87 Self { kind }
88 }
89
90 pub fn symbol(&self) -> Symbol {
92 self.kind.symbol()
93 }
94
95 pub fn value(kind: SkillFunctionKind) -> Arc<Self> {
97 Arc::new(Self::new(kind))
98 }
99}
100
101impl Object for SkillFunction {
102 fn display(&self, _cx: &mut Cx) -> Result<String> {
103 Ok(format!("#<function {}>", self.symbol()))
104 }
105
106 fn as_any(&self) -> &dyn std::any::Any {
107 self
108 }
109}
110
111impl ObjectCompat for SkillFunction {
112 fn as_callable(&self) -> Option<&dyn Callable> {
113 Some(self)
114 }
115}
116
117impl Callable for SkillFunction {
118 fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
119 match self.kind {
120 SkillFunctionKind::Install => install(cx, args),
121 SkillFunctionKind::Bind => bind(cx, args),
122 SkillFunctionKind::List => list(cx),
123 SkillFunctionKind::Card => card(cx, args),
124 SkillFunctionKind::Call => call(cx, args),
125 #[cfg(any(feature = "cache", feature = "cassette"))]
126 SkillFunctionKind::Audit => audit(cx),
127 #[cfg(feature = "agent")]
128 SkillFunctionKind::AsTool => crate::agent::as_tool(cx, args),
129 #[cfg(feature = "mcp")]
130 SkillFunctionKind::McpTools => crate::mcp::ops::mcp_tools(cx, args),
131 #[cfg(feature = "mcp")]
132 SkillFunctionKind::McpCall => crate::mcp::ops::mcp_call(cx, args),
133 #[cfg(feature = "openai")]
134 SkillFunctionKind::OpenAiTool => crate::openai::openai_tool(cx, args),
135 #[cfg(feature = "openai")]
136 SkillFunctionKind::OpenAiTools => crate::openai::openai_tools(cx, args),
137 #[cfg(feature = "runner")]
138 SkillFunctionKind::AsRunner => crate::runner::as_runner(cx, args),
139 #[cfg(feature = "serve")]
140 SkillFunctionKind::ServeMcp => crate::serve::serve_mcp(cx, args),
141 }
142 }
143
144 fn browse_args_shape(&self, _cx: &mut Cx) -> Result<Option<sim_kernel::ShapeRef>> {
145 let shape: Arc<dyn Shape> = match self.kind {
146 SkillFunctionKind::List => Arc::new(ListShape::new(Vec::new())),
147 #[cfg(any(feature = "cache", feature = "cassette"))]
148 SkillFunctionKind::Audit => Arc::new(ListShape::new(Vec::new())),
149 #[cfg(feature = "mcp")]
150 SkillFunctionKind::McpTools => Arc::new(ListShape::new(Vec::new())),
151 SkillFunctionKind::Card => Arc::new(ListShape::new(vec![Arc::new(AnyShape)])),
152 SkillFunctionKind::Install | SkillFunctionKind::Bind | SkillFunctionKind::Call => {
153 Arc::new(AnyShape)
154 }
155 #[cfg(feature = "agent")]
156 SkillFunctionKind::AsTool => Arc::new(ListShape::new(vec![Arc::new(AnyShape)])),
157 #[cfg(feature = "mcp")]
158 SkillFunctionKind::McpCall => Arc::new(ListShape::new(vec![Arc::new(AnyShape)])),
159 #[cfg(feature = "openai")]
160 SkillFunctionKind::OpenAiTool => Arc::new(ListShape::new(vec![Arc::new(AnyShape)])),
161 #[cfg(feature = "openai")]
162 SkillFunctionKind::OpenAiTools => Arc::new(AnyShape),
163 #[cfg(feature = "runner")]
164 SkillFunctionKind::AsRunner => Arc::new(ListShape::new(vec![Arc::new(AnyShape)])),
165 #[cfg(feature = "serve")]
166 SkillFunctionKind::ServeMcp => Arc::new(AnyShape),
167 };
168 Ok(Some(shape_value(
169 Symbol::qualified(self.symbol().to_string(), "args"),
170 shape,
171 )))
172 }
173
174 fn browse_result_shape(&self, _cx: &mut Cx) -> Result<Option<sim_kernel::ShapeRef>> {
175 Ok(Some(shape_value(
176 Symbol::qualified(self.symbol().to_string(), "result"),
177 Arc::new(AnyShape),
178 )))
179 }
180}
181
182pub fn skill_exports() -> Vec<Export> {
185 let symbols = vec![
186 skill_install_symbol(),
187 skill_bind_symbol(),
188 skill_list_symbol(),
189 skill_card_symbol(),
190 skill_call_symbol(),
191 ];
192 #[cfg(any(feature = "cache", feature = "cassette"))]
193 let symbols = {
194 let mut symbols = symbols;
195 symbols.push(skill_audit_symbol());
196 symbols
197 };
198 #[cfg(feature = "agent")]
199 let symbols = {
200 let mut symbols = symbols;
201 symbols.push(crate::agent::skill_as_tool_symbol());
202 symbols
203 };
204 #[cfg(feature = "mcp")]
205 let symbols = {
206 let mut symbols = symbols;
207 symbols.extend([
208 crate::mcp::skill_mcp_tools_symbol(),
209 crate::mcp::skill_mcp_call_symbol(),
210 ]);
211 symbols
212 };
213 #[cfg(feature = "openai")]
214 let symbols = {
215 let mut symbols = symbols;
216 symbols.extend([
217 crate::openai::skill_openai_tool_symbol(),
218 crate::openai::skill_openai_tools_symbol(),
219 ]);
220 symbols
221 };
222 #[cfg(feature = "runner")]
223 let symbols = {
224 let mut symbols = symbols;
225 symbols.push(crate::runner::skill_as_runner_symbol());
226 symbols
227 };
228 #[cfg(feature = "serve")]
229 let symbols = {
230 let mut symbols = symbols;
231 symbols.push(crate::serve::skill_serve_mcp_symbol());
232 symbols
233 };
234 symbols
235 .into_iter()
236 .map(|symbol| Export::Function {
237 symbol,
238 function_id: None,
239 })
240 .chain(std::iter::once(Export::Value {
241 symbol: crate::skill_registry_symbol(),
242 }))
243 .collect()
244}
245
246fn install(cx: &mut Cx, args: Args) -> Result<Value> {
247 cx.require(&skill_install_capability())?;
248 let values = args.into_vec();
249 let Some((transport_value, card_values)) = values.split_first() else {
250 return Err(Error::Eval(
251 "skill/install expects a transport and one or more SkillCard values".to_owned(),
252 ));
253 };
254 let registry = crate::skill_registry(cx)?;
255 registry.install_transport(transport_from_value(transport_value)?)?;
256 bind_cards(cx, ®istry, card_values)
257}
258
259fn bind(cx: &mut Cx, args: Args) -> Result<Value> {
260 cx.require(&skill_bind_capability())?;
261 let values = args.into_vec();
262 let registry = crate::skill_registry(cx)?;
263 bind_cards(cx, ®istry, &values)
264}
265
266fn bind_cards(
267 cx: &mut Cx,
268 registry: &crate::SkillRegistry,
269 card_values: &[Value],
270) -> Result<Value> {
271 if card_values.is_empty() {
272 return Err(Error::Eval(
273 "skill/bind expects one or more SkillCard values".to_owned(),
274 ));
275 }
276 let cards = card_values
277 .iter()
278 .map(|value| card_from_value(cx, value))
279 .collect::<Result<Vec<_>>>()?;
280 for card in &cards {
281 registry.bind_card(cx, card.clone())?;
282 }
283 let values = cards
284 .iter()
285 .map(|card| card.value(cx))
286 .collect::<Result<Vec<_>>>()?;
287 cx.factory().list(values)
288}
289
290fn list(cx: &mut Cx) -> Result<Value> {
291 let registry = crate::skill_registry(cx)?;
292 let cards = registry.cards()?;
293 let values = cards
294 .iter()
295 .map(|card| card.value(cx))
296 .collect::<Result<Vec<_>>>()?;
297 cx.factory().list(values)
298}
299
300fn card(cx: &mut Cx, args: Args) -> Result<Value> {
301 let target = one_arg(args, "skill/card expects a skill id or symbol")?;
302 let registry = crate::skill_registry(cx)?;
303 let found = match target_from_value(cx, target)? {
304 SkillTarget::Id(id) => registry.card_by_id(&id)?,
305 SkillTarget::Symbol(symbol) => registry.card_by_symbol(&symbol)?,
306 };
307 match found {
308 Some(card) => card.value(cx),
309 None => cx.factory().nil(),
310 }
311}
312
313fn call(cx: &mut Cx, args: Args) -> Result<Value> {
314 let values = args.into_vec();
315 let Some((target, rest)) = values.split_first() else {
316 return Err(Error::Eval(
317 "skill/call expects a skill id or symbol followed by arguments".to_owned(),
318 ));
319 };
320 let registry = crate::skill_registry(cx)?;
321 let card = match target_from_value(cx, target.clone())? {
322 SkillTarget::Id(id) => registry.card_by_id(&id)?,
323 SkillTarget::Symbol(symbol) => registry.card_by_symbol(&symbol)?,
324 }
325 .ok_or_else(|| Error::Eval("unknown skill".to_owned()))?;
326 cx.call_function(&card.symbol, Args::new(rest.to_vec()))
327}
328
329#[cfg(any(feature = "cache", feature = "cassette"))]
330fn audit(cx: &mut Cx) -> Result<Value> {
331 cx.require(&skill_audit_capability())?;
332 crate::skill_registry(cx)?.audit_values(cx)
333}
334
335fn one_arg(args: Args, message: &'static str) -> Result<Value> {
336 let mut values = args.into_vec();
337 if values.len() == 1 {
338 Ok(values.remove(0))
339 } else {
340 Err(Error::Eval(message.to_owned()))
341 }
342}
343
344enum SkillTarget {
345 Id(String),
346 Symbol(Symbol),
347}
348
349fn target_from_value(cx: &mut Cx, value: Value) -> Result<SkillTarget> {
350 match value.object().as_expr(cx)? {
351 sim_kernel::Expr::String(id) => Ok(SkillTarget::Id(id)),
352 sim_kernel::Expr::Symbol(symbol) => Ok(SkillTarget::Symbol(symbol)),
353 _ => Err(Error::TypeMismatch {
354 expected: "skill id or symbol",
355 found: "invalid target",
356 }),
357 }
358}
359
360pub fn skill_install_symbol() -> Symbol {
362 Symbol::qualified("skill", "install")
363}
364
365pub fn skill_bind_symbol() -> Symbol {
367 Symbol::qualified("skill", "bind")
368}
369
370pub fn skill_list_symbol() -> Symbol {
372 Symbol::qualified("skill", "list")
373}
374
375pub fn skill_card_symbol() -> Symbol {
377 Symbol::qualified("skill", "card")
378}
379
380pub fn skill_call_symbol() -> Symbol {
382 Symbol::qualified("skill", "call")
383}
384
385#[cfg(any(feature = "cache", feature = "cassette"))]
387pub fn skill_audit_symbol() -> Symbol {
388 Symbol::qualified("skill", "audit")
389}
390
391pub fn skill_install_capability() -> CapabilityName {
393 CapabilityName::new("skill.install")
394}
395
396pub fn skill_bind_capability() -> CapabilityName {
398 CapabilityName::new("skill.bind")
399}
400
401pub fn skill_call_capability() -> CapabilityName {
403 CapabilityName::new("skill.call")
404}
405
406pub fn skill_serve_capability() -> CapabilityName {
408 CapabilityName::new("skill.serve")
409}
410
411#[cfg(any(feature = "cache", feature = "cassette"))]
413pub fn skill_audit_capability() -> CapabilityName {
414 CapabilityName::new("skill.audit")
415}
416
417pub fn skill_specific_call_capability(id: &str) -> CapabilityName {
419 CapabilityName::new(format!("skill.{id}.call"))
420}