1use sim_citizen_derive::non_citizen;
2use sim_kernel::{CapabilityName, Cx, Expr, Object, ObjectCompat, Result, ShapeRef, Symbol, Value};
3
4#[derive(Clone, Debug, PartialEq, Eq)]
9pub enum SkillRole {
10 Tool,
12 Model,
14 Resource,
16 Prompt,
18 Memory,
20 Retriever,
22 Judge,
24 Router,
26}
27
28impl SkillRole {
29 pub fn as_symbol(&self) -> Symbol {
31 Symbol::new(match self {
32 Self::Tool => "tool",
33 Self::Model => "model",
34 Self::Resource => "resource",
35 Self::Prompt => "prompt",
36 Self::Memory => "memory",
37 Self::Retriever => "retriever",
38 Self::Judge => "judge",
39 Self::Router => "router",
40 })
41 }
42}
43
44#[derive(Clone, Debug, PartialEq, Eq)]
46pub enum SkillPrivacyPolicy {
47 MetadataOnly,
49 NoRaw,
51 LocalOnly,
53 AllowRaw,
55}
56
57impl SkillPrivacyPolicy {
58 pub fn as_symbol(&self) -> Symbol {
60 Symbol::new(match self {
61 Self::MetadataOnly => "metadata-only",
62 Self::NoRaw => "no-raw",
63 Self::LocalOnly => "local-only",
64 Self::AllowRaw => "allow-raw",
65 })
66 }
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
74pub enum SkillCacheMode {
75 Disabled,
77 ReadThrough,
79 ReadOnly,
81 WriteOnly,
83 Refresh,
85}
86
87impl SkillCacheMode {
88 pub fn as_symbol(&self) -> Symbol {
90 Symbol::new(match self {
91 Self::Disabled => "disabled",
92 Self::ReadThrough => "read-through",
93 Self::ReadOnly => "read-only",
94 Self::WriteOnly => "write-only",
95 Self::Refresh => "refresh",
96 })
97 }
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
105pub enum SkillCassetteMode {
106 Disabled,
108 RecordReplay,
110 ReplayOnly,
112 RecordOnly,
114}
115
116impl SkillCassetteMode {
117 pub fn as_symbol(&self) -> Symbol {
119 Symbol::new(match self {
120 Self::Disabled => "disabled",
121 Self::RecordReplay => "record-replay",
122 Self::ReplayOnly => "replay-only",
123 Self::RecordOnly => "record-only",
124 })
125 }
126}
127
128#[derive(Clone, Debug, PartialEq, Eq)]
130pub struct SkillPolicy {
131 pub privacy: SkillPrivacyPolicy,
133 pub cache: SkillCacheMode,
135 pub cassette: SkillCassetteMode,
137 pub idempotent: bool,
140 pub semantic_key: Option<String>,
143}
144
145impl Default for SkillPolicy {
146 fn default() -> Self {
147 Self {
148 privacy: SkillPrivacyPolicy::NoRaw,
149 cache: SkillCacheMode::Disabled,
150 cassette: SkillCassetteMode::Disabled,
151 idempotent: false,
152 semantic_key: None,
153 }
154 }
155}
156
157#[derive(Clone)]
167#[non_citizen(
168 reason = "shape-bearing runtime skill card; serializable projection is skill/Card descriptor",
169 kind = "handle",
170 descriptor = "skill/Card"
171)]
172pub struct SkillCard {
173 pub id: String,
175 pub symbol: Symbol,
177 pub aliases: Vec<Symbol>,
179 pub origin: Symbol,
181 pub title: String,
183 pub description: String,
185 pub input_shape: ShapeRef,
187 pub output_shape: ShapeRef,
189 pub roles: Vec<SkillRole>,
191 pub capabilities: Vec<CapabilityName>,
193 pub policy: SkillPolicy,
195 pub transport_id: String,
197 pub transport_kind: String,
199 pub operation: String,
201}
202
203pub struct FixtureSkillSpec {
205 pub id: String,
207 pub symbol: Symbol,
209 pub title: String,
211 pub description: String,
213 pub input_shape: ShapeRef,
215 pub output_shape: ShapeRef,
217 pub transport_id: String,
219 pub operation: String,
221}
222
223impl SkillCard {
224 pub fn fixture(spec: FixtureSkillSpec) -> Self {
229 let id = spec.id;
230 Self {
231 capabilities: vec![crate::skill_specific_call_capability(&id)],
232 id,
233 symbol: spec.symbol,
234 aliases: Vec::new(),
235 origin: Symbol::new("fixture"),
236 title: spec.title,
237 description: spec.description,
238 input_shape: spec.input_shape,
239 output_shape: spec.output_shape,
240 roles: vec![SkillRole::Tool],
241 policy: SkillPolicy::default(),
242 transport_id: spec.transport_id,
243 transport_kind: "fixture".to_owned(),
244 operation: spec.operation,
245 }
246 }
247
248 pub fn with_capability(mut self, capability: CapabilityName) -> Self {
250 self.capabilities.push(capability);
251 self
252 }
253
254 pub fn with_role(mut self, role: SkillRole) -> Self {
256 if !self.roles.contains(&role) {
257 self.roles.push(role);
258 }
259 self
260 }
261
262 pub fn with_policy(mut self, policy: SkillPolicy) -> Self {
264 self.policy = policy;
265 self
266 }
267
268 pub fn with_cache_mode(mut self, cache: SkillCacheMode) -> Self {
270 self.policy.cache = cache;
271 self
272 }
273
274 pub fn with_cassette_mode(mut self, cassette: SkillCassetteMode) -> Self {
276 self.policy.cassette = cassette;
277 self
278 }
279
280 pub fn with_idempotent(mut self, idempotent: bool) -> Self {
282 self.policy.idempotent = idempotent;
283 self
284 }
285
286 pub fn with_semantic_key(mut self, semantic_key: impl Into<String>) -> Self {
288 self.policy.semantic_key = Some(semantic_key.into());
289 self
290 }
291
292 pub fn with_privacy(mut self, privacy: SkillPrivacyPolicy) -> Self {
294 self.policy.privacy = privacy;
295 self
296 }
297
298 pub fn value(&self, cx: &mut Cx) -> Result<Value> {
300 cx.factory().opaque(std::sync::Arc::new(self.clone()))
301 }
302
303 pub fn table_value(&self, cx: &mut Cx) -> Result<Value> {
305 let aliases = cx.factory().list(
306 self.aliases
307 .iter()
308 .map(|alias| cx.factory().symbol(alias.clone()))
309 .collect::<Result<Vec<_>>>()?,
310 )?;
311 let roles = cx.factory().list(
312 self.roles
313 .iter()
314 .map(|role| cx.factory().symbol(role.as_symbol()))
315 .collect::<Result<Vec<_>>>()?,
316 )?;
317 let capabilities = cx.factory().list(
318 self.capabilities
319 .iter()
320 .map(|capability| cx.factory().string(capability.as_str().to_owned()))
321 .collect::<Result<Vec<_>>>()?,
322 )?;
323 let transport = cx.factory().table(vec![
324 (
325 Symbol::new("id"),
326 cx.factory().string(self.transport_id.clone())?,
327 ),
328 (
329 Symbol::new("kind"),
330 cx.factory()
331 .symbol(Symbol::new(self.transport_kind.clone()))?,
332 ),
333 (
334 Symbol::new("operation"),
335 cx.factory().string(self.operation.clone())?,
336 ),
337 ])?;
338 let mut policy = vec![
339 (
340 Symbol::new("privacy"),
341 cx.factory().symbol(self.policy.privacy.as_symbol())?,
342 ),
343 (
344 Symbol::new("cache"),
345 cx.factory().symbol(self.policy.cache.as_symbol())?,
346 ),
347 (
348 Symbol::new("cassette"),
349 cx.factory().symbol(self.policy.cassette.as_symbol())?,
350 ),
351 (
352 Symbol::new("idempotent"),
353 cx.factory().bool(self.policy.idempotent)?,
354 ),
355 ];
356 if let Some(semantic_key) = &self.policy.semantic_key {
357 policy.push((
358 Symbol::new("semantic-key"),
359 cx.factory().string(semantic_key.clone())?,
360 ));
361 }
362 let policy = cx.factory().table(policy)?;
363 cx.factory().table(vec![
364 (
365 Symbol::new("kind"),
366 cx.factory().symbol(Symbol::qualified("skill", "card"))?,
367 ),
368 (Symbol::new("id"), cx.factory().string(self.id.clone())?),
369 (
370 Symbol::new("symbol"),
371 cx.factory().symbol(self.symbol.clone())?,
372 ),
373 (Symbol::new("aliases"), aliases),
374 (
375 Symbol::new("origin"),
376 cx.factory().symbol(self.origin.clone())?,
377 ),
378 (
379 Symbol::new("title"),
380 cx.factory().string(self.title.clone())?,
381 ),
382 (
383 Symbol::new("description"),
384 cx.factory().string(self.description.clone())?,
385 ),
386 (Symbol::new("input-shape"), self.input_shape.clone()),
387 (Symbol::new("output-shape"), self.output_shape.clone()),
388 (Symbol::new("roles"), roles),
389 (Symbol::new("capabilities"), capabilities),
390 (Symbol::new("policy"), policy),
391 (Symbol::new("transport"), transport),
392 ])
393 }
394}
395
396impl Object for SkillCard {
397 fn display(&self, _cx: &mut Cx) -> Result<String> {
398 Ok(format!("#<skill-card {}>", self.id))
399 }
400
401 fn as_any(&self) -> &dyn std::any::Any {
402 self
403 }
404}
405
406impl ObjectCompat for SkillCard {
407 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
408 self.to_expr(cx)
409 }
410
411 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
412 self.table_value(cx)
413 }
414}